package com.yanqu.road.server.channelserver.manager.activity.washe;

import com.google.protobuf.AbstractMessage;
import com.yanqu.road.dao.impl.channelcenter.ChannelActivityDaoImpl;
import com.yanqu.road.dao.impl.channelcenter.washe.WasheBoxConfigDaoImpl;
import com.yanqu.road.dao.impl.channelcenter.washe.WasheCardConfigDaoImpl;
import com.yanqu.road.dao.impl.channelcenter.washe.WasheHonorMomentConfigDaoImpl;
import com.yanqu.road.dao.impl.channelcenter.washe.WasheModeConfigDaoImpl;
import com.yanqu.road.dao.impl.channelcenter.washe.WasheRankConfigDaoImpl;
import com.yanqu.road.dao.impl.channelcenter.washe.WasheRoundConfigDaoImpl;
import com.yanqu.road.dao.impl.channelcenter.washe.WasheSceneConfigDaoImpl;
import com.yanqu.road.dao.impl.channelcenter.washe.WasheSeasonConfigDaoImpl;
import com.yanqu.road.dao.impl.channelcenter.washe.WasheSkillConfigDaoImpl;
import com.yanqu.road.entity.activity.ActivityConfig;
import com.yanqu.road.entity.activity.ActivityInfo;
import com.yanqu.road.entity.activity.area.wase.ChannelWashePkUserData;
import com.yanqu.road.entity.activity.entername.UserEnterName;
import com.yanqu.road.entity.activity.washe.config.WasheCardConfig;
import com.yanqu.road.entity.activity.washe.config.WasheConfig;
import com.yanqu.road.entity.activity.washe.config.WasheModeConfig;
import com.yanqu.road.entity.activity.washe.config.WasheRankConfig;
import com.yanqu.road.entity.activity.washe.config.WasheRoundConfig;
import com.yanqu.road.entity.activity.washe.config.WasheSceneConfig;
import com.yanqu.road.entity.activity.washe.config.WasheSeasonConfig;
import com.yanqu.road.entity.activity.washe.data.WasheSceneData;
import com.yanqu.road.entity.activity.washe.data.WasheUserKey;
import com.yanqu.road.entity.activity.washe.data.WasheUserMatchData;
import com.yanqu.road.entity.activity.washe.eliminate.WasheBlock;
import com.yanqu.road.entity.activity.washe.eliminate.WasheBlockEffect;
import com.yanqu.road.entity.activity.washe.eliminate.WasheGameData;
import com.yanqu.road.entity.activity.washe.eliminate.WasheMoveParam;
import com.yanqu.road.entity.activity.washe.eliminate.WasheOperateResult;
import com.yanqu.road.entity.activity.washe.eliminate.WasheOperateRoundResult;
import com.yanqu.road.entity.activity.washe.eliminate.WasheOperateShow;
import com.yanqu.road.entity.activity.washe.eliminate.WasheOperateStageResult;
import com.yanqu.road.entity.activity.washe.eliminate.WasheUseSkillParam;
import com.yanqu.road.entity.activity.washe.game.WasheRoomData;
import com.yanqu.road.entity.activity.washe.game.WasheUserGameCard;
import com.yanqu.road.entity.activity.washe.game.WasheUserGameData;
import com.yanqu.road.entity.activity.washe.game.WasheUserGameSkill;
import com.yanqu.road.entity.activity.washe.jinbiao.WasheJinBiaoPeakBattleData;
import com.yanqu.road.entity.enums.eSeasonRewardType;
import com.yanqu.road.entity.enums.eSystemId;
import com.yanqu.road.entity.enums.washe.WasheGameOverType;
import com.yanqu.road.entity.enums.washe.eWasheBlockEffectType;
import com.yanqu.road.entity.enums.washe.eWasheBlockType;
import com.yanqu.road.entity.enums.washe.eWasheColorType;
import com.yanqu.road.entity.enums.washe.eWasheEffectType;
import com.yanqu.road.entity.enums.washe.eWasheFriendNoticeType;
import com.yanqu.road.entity.enums.washe.eWasheFunction;
import com.yanqu.road.entity.enums.washe.eWasheJinBiaoPeakBattleResultType;
import com.yanqu.road.entity.enums.washe.eWasheJinBiaoPeakNoticeType;
import com.yanqu.road.entity.enums.washe.eWasheLogType;
import com.yanqu.road.entity.enums.washe.eWasheModeType;
import com.yanqu.road.entity.enums.washe.eWasheOperateType;
import com.yanqu.road.entity.enums.washe.eWasheSceneType;
import com.yanqu.road.entity.enums.washe.eWasheSkillType;
import com.yanqu.road.entity.log.LogWasheOperate;
import com.yanqu.road.entity.player.UserBaseInfo;
import com.yanqu.road.entity.rank.xrank.XSeasonStageUserRank;
import com.yanqu.road.entity.rank.xrank.XSeasonUserRank;
import com.yanqu.road.logic.bussiness.activity.WasheBusiness;
import com.yanqu.road.logic.config.ChannelConfig;
import com.yanqu.road.logic.helper.SeasonHelper;
import com.yanqu.road.logic.pb.PlayerBasePb;
import com.yanqu.road.logic.pb.WashePb2;
import com.yanqu.road.logic.xlogic.rank.season.XSeasonUserRankListModel;
import com.yanqu.road.logic.xlogic.rank.season.XSeasonUserRankMap;
import com.yanqu.road.logic.xlogic.rank.season.stage.XSeasonStageUserRankListModel;
import com.yanqu.road.logic.xlogic.rank.season.stage.XSeasonStageUserRankMap;
import com.yanqu.road.pb.activity.WasheProto;
import com.yanqu.road.server.channelserver.logic.ChannelMessageHelper;
import com.yanqu.road.server.channelserver.manager.activity.entername.ChannelEnterNameMgr;
import com.yanqu.road.server.channelserver.manager.activity.washe.game.WasheAiLogic;
import com.yanqu.road.server.channelserver.manager.activity.washe.game.WasheFirstGameLogic;
import com.yanqu.road.server.channelserver.manager.activity.washe.game.WasheGameLogic;
import com.yanqu.road.server.channelserver.manager.activity.washe.game.WasheSceneLogic;
import com.yanqu.road.server.channelserver.manager.activity.washe.msg.WasheNoticeHelper;
import com.yanqu.road.server.channelserver.manager.activity.washepk.ChannelWashPkActivity;
import com.yanqu.road.server.channelserver.manager.activity.washepk.ChannelWashePkMgr;
import com.yanqu.road.server.channelserver.manager.log.impl.ChannelLogWasheLogic;
import com.yanqu.road.server.channelserver.manager.server.ChannelXServerNameMgr;
import com.yanqu.road.server.channelserver.manager.xrank.ChannelXRankMgr;
import com.yanqu.road.server.protocol.ClientProtocol;
import com.yanqu.road.server.protocol.GameErrorCode;
import com.yanqu.road.server.protocol.GameProtocol;
import com.yanqu.road.utils.RandomHelper;
import com.yanqu.road.utils.date.DateHelper;
import com.yanqu.road.utils.string.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

public class ChannelWasheActivity {

    public static final Logger logger = LogManager.getLogger(ChannelWasheActivity.class.getName());
    public static final String DECORATION_SEPARATOR = "@#$";
    public static final String DECORATION_SPLIT_SEPARATOR = "@#\\$";

    private WasheConfig config;

    private final Object lock = new Object();
    private final AtomicLong roomMaxId = new AtomicLong(100);
    private final Random needLockRandom = new Random(); // 锁自己

    private final Map<String, WasheUserMatchData> matchingUserMap = new ConcurrentHashMap<>(); // 锁lock
    private final Map<String, WasheUserMatchData> roomUserMap = new ConcurrentHashMap<>(); // 锁lock
    private final Map<Long, WasheRoomData> roomMap = new ConcurrentHashMap<>(); // 锁自己
    private final LinkedList<Long> matchTimeList = new LinkedList<>();  // 锁自己

    private WasheSceneData sceneData; // 锁自己

    private Map<Integer, List<WasheJinBiaoPeakBattleData>> peakRoomDataMap = new ConcurrentHashMap<>();

    /**
     * 巅峰赛名单
     */
    private Map<Long, XSeasonUserRank> peakXSeasonUserRankMap = new ConcurrentHashMap<>();

    // --------------------------- 配置 --------------------------------

    public ChannelWasheActivity(ActivityInfo activityInfo) {
        this.config = createWasheConfig(activityInfo);
        reloadData();
        logger.info("load channel washe activtyId {}.", activityInfo.getActivityId());

        // 容错
        checkSceneDelete();
    }

    /**
     * 加载数据
     */
    public void reloadData() {
        int seasonId = config.getSeasonId();
        // 轮次名单数据
        peakRoomDataMap = WasheBusiness.getWasheJinBiaoPeakBattleDataMap(seasonId);

        // 盘面机制
        String channelKey = SeasonHelper.getWasheRangeKey(config.getActivityInfo().getParam6());
        sceneData = WasheBusiness.getWasheSceneData(channelKey);
        if (sceneData == null) {
            WasheSceneData tmpData = new WasheSceneData();
            tmpData.setChannelKey(channelKey);
            tmpData.setSceneList(new ArrayList<>());
            tmpData.setRefreshTime(0);
            tmpData.setInsertOption();
            sceneData = tmpData;
        }
    }

    public void reloadConfig(ActivityInfo activityInfo) {
        this.config = createWasheConfig(activityInfo);
        logger.info("reload channel washe config activtyId {}.", activityInfo.getActivityId());

        // 容错
        checkSceneDelete();
    }

    private WasheConfig createWasheConfig(ActivityInfo activityInfo) {
        int activityId = activityInfo.getActivityId();

        Map<Integer, Map<String, ActivityConfig>> mapMap = new ChannelActivityDaoImpl().getActivityConfigMap(Collections.singletonList(activityId));
        Map<String, ActivityConfig> activityConfigMap = mapMap.getOrDefault(activityId, new ConcurrentHashMap<>());

        WasheConfig tmpConfig = new WasheConfig();
        tmpConfig.setActivityInfo(activityInfo);
        tmpConfig.initActivityConfig(activityConfigMap, ChannelConfig.CHANNEL_ACTIVITY_TIME_ZONE);
        tmpConfig.setSeasonConfigMap(new WasheSeasonConfigDaoImpl().getWasheSeasonTimeConfigMap(activityId, ChannelConfig.CHANNEL_ACTIVITY_TIME_ZONE));
        tmpConfig.setRankConfigMap(new WasheRankConfigDaoImpl().getWasheRankConfigMap(activityId, tmpConfig.getLastSeasonId())); // 放在setSeasonConfigMap后面
        tmpConfig.setBoxConfigListMap(new WasheBoxConfigDaoImpl().getWasheBoxConfigListMap(activityId, tmpConfig.getLastSeasonId(), tmpConfig.getVersion())); // 放在setSeasonConfigMap后面
        tmpConfig.setModeConfigMap(new WasheModeConfigDaoImpl().getWasheModeConfigMap(activityId));
        tmpConfig.setSceneConfigMap(new WasheSceneConfigDaoImpl().getWasheSceneConfigMap(activityId));
        tmpConfig.setCardConfigMap(new WasheCardConfigDaoImpl().getWasheCardConfigMap(activityId));
        tmpConfig.setSkillConfigMap(new WasheSkillConfigDaoImpl().getWasheSkillConfigMap(activityId));
        tmpConfig.setHonorMomentConfigMap(new WasheHonorMomentConfigDaoImpl().getWasheHonorMomentConfigMap(activityId));
        tmpConfig.setRoundConfigMap(new WasheRoundConfigDaoImpl().getWasheRoundConfigMap(activityId, tmpConfig.getLastSeasonId(), ChannelConfig.CHANNEL_ACTIVITY_TIME_ZONE));
        return tmpConfig;
    }

    /**
     * 生成巅峰赛名单
     */
    public void jinBiaoPeakGenDataByTimer() {
        synchronized (peakRoomDataMap) {
            // 判断是否过了巅峰赛开始时间
            long nowTime = System.currentTimeMillis();
            WasheSeasonConfig seasonConfig = config.getWasheSeasonConfig();
            if (seasonConfig == null) {
                return;
            }
            if (!seasonConfig.isDianFengOpen()) {
                return;
            }
            // 积分赛结束到巅峰赛结束期间生成
            if (seasonConfig.getIntegralEndTime() > nowTime || nowTime > seasonConfig.getPeakednessEndTime()) {
                return;
            }
            // 缓存下积分赛的榜单 -->巅峰赛名单
            if (peakXSeasonUserRankMap.size() == 0) {
                // 生成一份名单
                XSeasonUserRankMap seasonUserRankMap = ChannelXRankMgr.getSeasonUserRankMap();
                int seasonId = config.getSeasonId();
                String rangeKey = SeasonHelper.getWasheRangeKey(config.getActivityInfo().getParam6());
                XSeasonUserRankListModel seasonUserRankListModel = seasonUserRankMap.getUserRankListModel(eSystemId.WasheActivity.getValue(), seasonId, eSeasonRewardType.JinBiaoScoreRank.getIntValue(), rangeKey);
                // 取积分赛前64名
                List<XSeasonUserRank> allRankList = seasonUserRankListModel.getRankList(config.getJinBiaoTopPeopleNum());
                // 打乱一下
                Collections.shuffle(allRankList);
                for (XSeasonUserRank xSeasonUserRank : allRankList) {
                    peakXSeasonUserRankMap.put(xSeasonUserRank.getUserId(), xSeasonUserRank);
                }
            }
            // 获取所有的巅峰赛轮次
            List<Integer> needGenRoundList = new ArrayList<>();
            for (Map.Entry<Integer, WasheRoundConfig> entry : config.getRoundConfigMap().entrySet()) {
                int round = entry.getKey();
                WasheRoundConfig roundConfig = entry.getValue();
                if (peakRoomDataMap.containsKey(round)) {
                    // 已生成过的不再处理
                    continue;
                }
                // 已经过去的不处理
                if (roundConfig.getWarTime() < nowTime) {
                    continue;
                }
                needGenRoundList.add(round);
            }
            if (needGenRoundList.size() > 0) {
                // 记录下已匹配的人
                Map<Long, List<Long>> hadMatchUserIdMap = new HashMap<>();

                for (int round : needGenRoundList) {
                    // 拷贝一份数据
                    List<Long> userIdList = new ArrayList<>(peakXSeasonUserRankMap.keySet());
                    // 打乱一下
                    Collections.shuffle(userIdList);
                    if (!peakRoomDataMap.containsKey(round)) {
                        peakRoomDataMap.put(round, new ArrayList<>());
                    }
                    while (userIdList.size() >= 2) {
                        long userId1 = userIdList.remove(0);
                        // 随机获取一个没有匹配过的人
                        // 已匹配过的人
                        List<Long> hadMatchList1 = hadMatchUserIdMap.getOrDefault(userId1, new ArrayList<>());
                        long userId2 = randomMatchUserId(userIdList, hadMatchList1);
                        if (userId2 == 0) {
                            // 做个容错，匹配不到，随机匹配一个
                            userId2 = userIdList.remove(0);
                        } else {
                            // 从列表中移除
                            userIdList.remove(userId2);
                        }
                        // 加到user1已匹配的记录中
                        hadMatchList1.add(userId2);
                        // user2已匹配过的人
                        List<Long> hadMatchList2 = hadMatchUserIdMap.getOrDefault(userId2, new ArrayList<>());
                        // 加到user2已匹配的记录中
                        hadMatchList2.add(userId1);
                        hadMatchUserIdMap.put(userId1, hadMatchList1);
                        hadMatchUserIdMap.put(userId2, hadMatchList2);
                        WasheJinBiaoPeakBattleData battleData = new WasheJinBiaoPeakBattleData();
                        battleData.setBattleResult(0);
                        battleData.setUserId1(userId1);
                        battleData.setServerId1(peakXSeasonUserRankMap.get(userId1).getServerId());
                        battleData.setUserId2(userId2);
                        battleData.setServerId2(peakXSeasonUserRankMap.get(userId2).getServerId());
                        battleData.setRound(round);
                        battleData.setSeasonId(config.getSeasonId());
                        battleData.setInsertOption();
                        peakRoomDataMap.get(round).add(battleData);
                    }
                }
            }
        }
    }

    private long randomMatchUserId(List<Long> userIdList, List<Long> hadMatchList) {
        RandomHelper randomHelper = new RandomHelper();
        List<Long> list = new ArrayList<>();
        for (Long userId : userIdList) {
            if (hadMatchList.contains(userId)) {
                continue;
            }
            list.add(userId);
        }
        if (list.size() == 0) {
            return 0;
        }
        return list.get(randomHelper.next(0, list.size()));
    }

    /**
     * 锦标赛巅峰赛创建房间
     */
    public void jinBiaoPeakCreateRoomByTimer() {
        synchronized (peakRoomDataMap) {
            long nowTime = System.currentTimeMillis();
            WasheSeasonConfig seasonConfig = config.getWasheSeasonConfig();
            if (seasonConfig == null) {
                return;
            }
            if (!seasonConfig.isDianFengOpen()) {
                return;
            }
            // 检测有没有房间数据,没有的话创建下房间
            for (Map.Entry<Integer, List<WasheJinBiaoPeakBattleData>> entry : peakRoomDataMap.entrySet()) {
                int round = entry.getKey();
                WasheRoundConfig roundConfig = config.getRoundConfigMap().get(round);
                if (roundConfig == null) {
                    continue;
                }
                // 备战阶段
                if (nowTime < roundConfig.getPreparationTime()) {
                    continue;
                }
                List<WasheJinBiaoPeakBattleData> battleDataList = entry.getValue();
                for (WasheJinBiaoPeakBattleData battleData : battleDataList) {
                    if ((battleData.getBattleResult() == eWasheJinBiaoPeakBattleResultType.NotStart.getValue() || battleData.getBattleResult() == eWasheJinBiaoPeakBattleResultType.InBattle.getValue()) && battleData.getRoomData() == null) {
                        // 创建房间
                        WasheRoomData roomData = createRoom(eWasheModeType.JinBiaoPeak, roundConfig.getSceneSkillId());
                        putRoomToMap(roomData);
                        battleData.setRoomData(roomData);
                    }
                }
            }
        }
    }

    /**
     * 积分赛阶段性名单
     */
    public void jinBiaoScoreStageByTimer() {
        if (config == null) {
            return;
        }
        WasheSeasonConfig seasonConfig = config.getWasheSeasonConfig();
        if (seasonConfig == null) {
            return;
        }
        if (!seasonConfig.isJiFenOpen()) {
            return;
        }
        // 结算积分赛阶段榜单
        XSeasonStageUserRankMap seasonStageUserRankMap = ChannelXRankMgr.getSeasonStageUserRankMap();
        // 复制一份出来,生成一份名单
        XSeasonUserRankMap seasonUserRankMap = ChannelXRankMgr.getSeasonUserRankMap();
        int systemId = eSystemId.WasheActivity.getValue();
        int seasonId = config.getSeasonId();
        int type = eSeasonRewardType.JinBiaoScoreRank.getIntValue();
        String rangeKey = SeasonHelper.getWasheRangeKey(config.getActivityInfo().getParam6());
        List<String> stageConditionParams = StringUtils.stringToStringList(config.getJinBiaoScoreStageCondition(), "\\|");
        for (int i = 0; i < stageConditionParams.size(); i++) {
            int stage = i + 1;
            String[] split = stageConditionParams.get(i).split(",");
            int beginTime = Integer.parseInt(split[0]);
            int maxRank = Integer.parseInt(split[1]);
            if (seasonConfig.getIntegralBeginTime() + DateHelper.SECOND_MILLIONS * beginTime > System.currentTimeMillis()) {
                continue;
            }
            // 判断下当前轮次有没有存在
            XSeasonStageUserRankListModel userRankListModel = seasonStageUserRankMap.getUserRankListModel(systemId, seasonId, type, stage, rangeKey);
            if (userRankListModel == null || userRankListModel.getRankList().size() == 0) {
                LinkedList<XSeasonStageUserRank> rankList = new LinkedList<>();
                XSeasonUserRankListModel seasonUserRankListModel = seasonUserRankMap.getUserRankListModel(systemId, seasonId, type, rangeKey);
                for (XSeasonUserRank xSeasonUserRank : seasonUserRankListModel.getRankList(maxRank)) {
                    rankList.add(new XSeasonStageUserRank(xSeasonUserRank, stage));
                }
                seasonStageUserRankMap.addXSeasonStageUserRankListModel(systemId, seasonId, type, stage, rangeKey, rankList);
            }
        }
    }

    /**
     * 巅峰赛开始游戏
     */
    public void jinBiaoPeakStartGameByTimer() {
        synchronized (peakRoomDataMap) {
            long nowTime = System.currentTimeMillis();
            WasheSeasonConfig seasonConfig = config.getWasheSeasonConfig();
            if (seasonConfig == null) {
                return;
            }
            if (!seasonConfig.isDianFengOpen()) {
                return;
            }
            for (Map.Entry<Integer, List<WasheJinBiaoPeakBattleData>> entry : peakRoomDataMap.entrySet()) {
                int round = entry.getKey();
                WasheRoundConfig roundConfig = config.getRoundConfigMap().get(round);
                if (roundConfig == null) {
                    continue;
                }
                // 开战阶段
                if (nowTime < roundConfig.getWarTime()) {
                    continue;
                }
                List<WasheJinBiaoPeakBattleData> battleDataList = entry.getValue();
                for (WasheJinBiaoPeakBattleData battleData : battleDataList) {
                    WasheRoomData roomData = battleData.getRoomData();
                    if (battleData.getBattleResult() != eWasheJinBiaoPeakBattleResultType.NotStart.getValue() || roomData == null) {
                        continue;
                    }
                    // 人没来的情况
                    if (roomData.getPlayer1() == null || roomData.getPlayer2() == null) {
                        // 两个都没来,直接结束
                        if (roomData.getPlayer1() == null && roomData.getPlayer2() == null) {
//                            // 设置状态为弃权
                            battleData.setBattleResult(eWasheJinBiaoPeakBattleResultType.GiveUp.getValue());
                            removeRoom(roomData);
                            continue;
                        } else {
                            // 一方没来且超过等待时间，处理下托管
                            if (nowTime > roundConfig.getWarTime() + config.getJinBiaoWaitingTime() * DateHelper.SECOND_MILLIONS) {
                                if (roomData.getPlayer1() == null) {
                                    // 创建一个小玉跟他打
                                    WasheUserMatchData userMatchData = roomData.getPlayer2().getUserMatchData();
                                    handlePeakRobot(roomData, true, battleData.getUserId1(), userMatchData.getSkills());
                                    battleData.setBattleResult(eWasheJinBiaoPeakBattleResultType.Player2Win.getValue());
                                    // 先确定下结果
                                    roomData.setPeakWinPlayer(2);
                                    roomData.setWinType(WasheGameOverType.GIVEUP);
                                } else {
                                    WasheUserMatchData userMatchData = roomData.getPlayer1().getUserMatchData();
                                    // 创建一个小玉跟他打
                                    handlePeakRobot(roomData, false, battleData.getUserId2(), userMatchData.getSkills());
                                    battleData.setBattleResult(eWasheJinBiaoPeakBattleResultType.Player1Win.getValue());
                                    // 先确定下结果
                                    roomData.setPeakWinPlayer(1);
                                    roomData.setWinType(WasheGameOverType.GIVEUP);
                                }
                            } else {
                                // 时间还没到，继续等待
                                continue;
                            }
                        }
                    }
                    startGame(roomData);

                    // 未分出结果的设置状态为对战中
                    if (battleData.getBattleResult() == eWasheJinBiaoPeakBattleResultType.NotStart.getValue()) {
                        battleData.setBattleResult(eWasheJinBiaoPeakBattleResultType.InBattle.getValue());
                    }

                    // 同步
                    List<WasheUserMatchData> list = new ArrayList<>();
                    list.add(roomData.getPlayer1().getUserMatchData());
                    list.add(roomData.getPlayer2().getUserMatchData());
                    WasheProto.WasheJinBiaoPeakSyncRoomRespMsg.Builder syncBuilder = WasheProto.WasheJinBiaoPeakSyncRoomRespMsg.newBuilder();
                    syncBuilder.setType(eWasheJinBiaoPeakNoticeType.StartGame.getValue());
                    for (WasheUserMatchData tmp : list) {
                        sendGamePacket(ClientProtocol.U_WASHE_JIN_BIAO_PEAK_SYNC_ROOM, syncBuilder, tmp);
                    }

                    // 让游戏服卡开始ping
                    for (WasheUserMatchData tmp : list) {
                        WasheProto.ChannelWasheStatusRespMsg.Builder statusBuilder = WasheProto.ChannelWasheStatusRespMsg.newBuilder();
                        statusBuilder.setType(1);
                        // 巅峰赛
                        statusBuilder.setModeId(eWasheModeType.JinBiaoPeak.getValue());
                        sendGamePacket(GameProtocol.S_WASHE_STATUS_FROM_CROSS, statusBuilder, tmp);
                    }

                }
            }
        }
    }

    /**
     * 小玉接管
     */
    public void handlePeakRobot(WasheRoomData roomData, boolean isPlayer1, long userId, List<Integer> skillList) {
        WasheUserGameData userGameData = isPlayer1 ? roomData.getPlayer1() : roomData.getPlayer2();
        if (userGameData != null && userGameData.isRobot()) {
            return;
        }
        // 拿下匹配数据
        WasheUserMatchData matchData = userGameData != null ? userGameData.getUserMatchData() : null;
        if (matchData == null) {
            // 如果是空的就说明没来，直接搞个用户数据
            matchData = new WasheUserMatchData();
            // 用户数据从积分榜拿
            XSeasonUserRank userRank = peakXSeasonUserRankMap.get(userId);
            String userKey = getKey(new WasheUserKey(userRank.getChannelId(), userRank.getServerId(), userRank.getUserId()));
            if (matchingUserMap.containsKey(userKey) || roomUserMap.containsKey(userKey)) {
                // 在房间内,生成个小玉,分数先写死一个，下面卡片也要替换掉
                matchData.setAi(true);
            } else {
                matchData.setUserId(userId);
                matchData.setChannelId(userRank.getChannelId());
                matchData.setServerId(userRank.getServerId());
                matchData.setKey(userKey);
            }
            matchData.setBaseInfo(userRank.getBaseInfo());
            matchData.setCardId(config.getJinBiaoPeakAiCardId());
            matchData.setSkills(new ArrayList<>(skillList));
            // 加入房间
            joinPeakRoom(roomData, matchData, isPlayer1);
            // 再拿一下用户数据
            userGameData = isPlayer1 ? roomData.getPlayer1() : roomData.getPlayer2();
        }
        if (userGameData != null) {
            // 记录小玉接管前的积分
            userGameData.setRobot(true);
            userGameData.setRobotScore(userGameData.getScore());
        }
        List<WasheUserMatchData> playerList = new ArrayList<>();
        if (roomData.getPlayer1() != null) {
            playerList.add(roomData.getPlayer1().getUserMatchData());
        }
        if (roomData.getPlayer2() != null) {
            playerList.add(roomData.getPlayer2().getUserMatchData());
        }
        // 发条通知
        for (WasheUserMatchData data : playerList) {
            if (data != null && !data.isAi()) {
                WasheProto.WasheSyncNoticeRespMsg.Builder builder = WasheProto.WasheSyncNoticeRespMsg.newBuilder();
                builder.setType(eWasheOperateType.TurnPlayer.getValue());
                builder.setOperate(WasheProto.WasheOperateResultMsg.newBuilder()
                        .setSide(roomData.getTurnPlayer())
                        .setSkillId(-1)
                        .setGameData(WashePb2.parseWasheRoomDataMsg(roomData, data.getKey())));
                sendNotice(builder, data, roomData.getModeId(), null);
            }
        }
    }


    // --------------------------- 匹配 --------------------------------

    public void matchByTimer() {
        synchronized (lock) {

            // 给空值避免报错
            // 匹配模式,段位ID,匹配数据
            Map<Integer, Map<Integer, List<WasheUserMatchData>>> rankUserList = new HashMap<>();
            Map<Integer, WasheRankConfig> rankConfigMap = config.getRankConfigMap();
            for (eWasheModeType modeType : eWasheModeType.values()) {
                if(modeType == eWasheModeType.AreaPk){
                    continue;
                }
                Map<Integer, List<WasheUserMatchData>> initRankList = new HashMap<>();
                for (WasheRankConfig rankConfig : rankConfigMap.values()) {
                    initRankList.put(rankConfig.getRankId(), new ArrayList<>());
                }
                rankUserList.put(modeType.getValue(), initRankList);
            }
            //地域，随机匹配
            rankUserList.put(eWasheModeType.AreaPk.getValue(), new HashMap<>());
            rankUserList.get(eWasheModeType.AreaPk.getValue()).put(0, new ArrayList<>());

            // 能否超时匹配人机
            boolean outTimeMatchNpc = true;
            if (eWasheFunction.NoSeasonNoOutTimeMatchNpc.isOpen(config.getVersion())) {
                outTimeMatchNpc = config.seasonInTime();
            }

            for (WasheUserMatchData matchData : matchingUserMap.values()) {
                int modeId = matchData.getModeId();
                if (modeId == eWasheModeType.JinBiaoScoreLow.getValue() || modeId == eWasheModeType.JinBiaoScoreHigh.getValue()) {
                    // 锦标赛
                    // 加入匹配
                    int rankId = config.getWasheRankConfig(matchData.getScore(), matchData.isNewHand()).getRankId();
                    rankUserList.get(modeId).get(rankId).add(matchData);
                } if(modeId == eWasheModeType.AreaPk.getValue()) {
                    rankUserList.get(modeId).get(0).add(matchData);
                }else {
                    // 常规匹配
                    if (matchData.getPlayTimes() <= 0) {
                        // 第一把
                        handleMatchNpc_First(matchData);
                    } else if (matchData.getScore() <= config.getMatchNpcScore()
                            || matchData.isNewHand() && System.currentTimeMillis() - matchData.getBeginMatchTime() > config.getNewHandMatchNpcTime()
                            || outTimeMatchNpc && System.currentTimeMillis() - matchData.getBeginMatchTime() > config.getMatchOutTime() && matchData.getScore() < config.getMatchOutScoreLimit()) {
                        // 低分数先匹配人机 || 新手阶段 && 匹配时间过长 || 可以超时匹配 && 匹配时间超过最大值 && 分数小于配置值
                        handleMatchNpc(matchData);
                    } else {
                        // 加入匹配
                        int rankId = config.getWasheRankConfig(matchData.getScore(), matchData.isNewHand()).getRankId();
                        rankUserList.get(modeId).get(rankId).add(matchData);
                    }
                }
            }
            // 匹配
            for (Map.Entry<Integer, Map<Integer, List<WasheUserMatchData>>> entry : rankUserList.entrySet()) {
                if(entry.getKey() == eWasheModeType.AreaPk.getValue()){
                    //地域随机匹配
                    matchPlayerRandom(entry.getValue());
                }else {
                    matchPlayerByRank(entry.getKey(), entry.getValue());
                }
            }
        }
    }

    private void matchPlayerRandom(Map<Integer, List<WasheUserMatchData>> value) {
        if(value.get(0).size() == 0){
            return;
        }
        List<WasheUserMatchData> list = new ArrayList<>(value.get(0));
        List<WasheUserMatchData> otherList = new ArrayList<>(value.get(0));
        // 打乱人员
        Collections.shuffle(list);
        Collections.shuffle(otherList);

        // 开始匹配
        for (WasheUserMatchData player1 : list) {
            if (otherList.size() < 2) {
                break;
            }
            ChannelWashPkActivity activity = ChannelWashePkMgr.getInTimeActivityInfo(player1.getChannelId(), player1.getServerId());
            if(activity == null){
                continue;
            }
            UserEnterName userEnterName = ChannelEnterNameMgr.getUserEnterName(activity.getActivityInfo().getActivityId(), player1.getUserId());
            if(userEnterName == null){
                continue;
            }
            //ChannelWashePkMgr.getLogger().info("++++++++++++++: 玩家 {}， 分组 {}", player1.getUserId(), userEnterName.getGroupId());

            // 取出被匹配的玩家
            if (!otherList.remove(player1)) {
                continue; // 已经被匹配走了
            }

            WasheUserMatchData player2 = null;
            //int groupId = 0;
            Iterator<WasheUserMatchData> iterator = otherList.iterator();
            while (iterator.hasNext()){
                WasheUserMatchData player = iterator.next();
                UserEnterName otherEnterName = ChannelEnterNameMgr.getUserEnterName(activity.getActivityInfo().getActivityId(), player.getUserId());
                if(otherEnterName == null){
                    continue;
                }
                if(otherEnterName.getGroupId() == userEnterName.getGroupId()){
                    continue;
                }
                //groupId = otherEnterName.getGroupId();
                player2 = player;
                iterator.remove();
                break;
            }

            if(player2 == null){
                continue;
            }
            //ChannelWashePkMgr.getLogger().info("++++++++++++++: 玩家 {}， 分组 {}, 匹配  玩家 {}， 分组 {}", player1.getUserId(), userEnterName.getGroupId(), player2.getUserId(), groupId);

            // 匹配完成
            handleMatchPlayer(eWasheModeType.AreaPk.getValue(), player1, player2);
        }
    }

    private void handleMatchNpc(WasheUserMatchData matchData) {
        WasheUserMatchData npc = createNpcMatchData(matchData.getCardId(), matchData.getSkills(), matchData.getScore(), matchData.isNewHand());

        matchingUserMap.remove(matchData.getKey());
        int randomSceneId = randomSceneId(matchData.getScore(), matchData.getScore());
        WasheRoomData roomData = createRoom(matchData.isNewHand() ? eWasheModeType.NewHandPvp : eWasheModeType.Pvp, randomSceneId);
        synchronized (roomData) {
            joinRoom(roomData, matchData);
            joinRoom(roomData, npc);
            putRoomToMap(roomData);
            startGame(roomData);
        }
    }

    // 新手第一把特殊处理
    private void handleMatchNpc_First(WasheUserMatchData matchData) {
        WasheUserMatchData npc = createNpcMatchData(matchData.getCardId(), matchData.getSkills(), matchData.getScore(), matchData.isNewHand());

        matchingUserMap.remove(matchData.getKey());
        WasheRoomData roomData = createRoom(eWasheModeType.Guide, eWasheSceneType.Empty.getType());
        synchronized (roomData) {
            joinRoom(roomData, matchData);
            joinRoom(roomData, npc);
            putRoomToMap(roomData);

            // 指定先手玩家
            int turnPlayer = 1;
            // 第一回合多给他半小时时间
            WasheModeConfig modeConfig = config.getModeConfigMap().get(roomData.getModeId());
            long clearTime = modeConfig.getClearTime();
            long expireTime = System.currentTimeMillis() + clearTime + DateHelper.MINUTE_MILLIONS * 30;
            // 拿初始盘面
            List<List<WasheBlock>> initBlockList = WasheFirstGameLogic.getBlockList(true, isPlayer1(roomData, matchData.getKey()));
            roomData.setRound(1);
            roomData.setExpireTime(expireTime);
            roomData.setTurnPlayer(turnPlayer);
            roomData.getPlayer1().setStep(modeConfig.getMaxStep() + 1);
            roomData.getPlayer2().setStep(modeConfig.getMaxStep());
            roomData.setGameData(WasheGameData.createWasheGameData(roomData.getAppointSceneId(), initBlockList));
            roomData.setBeginTime(System.currentTimeMillis());
            roomData.setBegin(true);

            WasheUserGameCard card = roomData.getPlayer1().getCard();
            card.setCardMp(card.getCardMaxMp() - 3);

            // 先同步游戏开始客户端才可以进去
            syncGameStart(roomData, roomData.getPlayer1().getUserMatchData(), roomData.getPlayer2().getUserMatchData());
        }
    }

    // 新手第一把特殊处理
    private void handleGuide_First(WasheUserMatchData matchData, WasheRoomData roomData, WasheModeConfig modeConfig) {
        if (roomData.isGuide() && !matchData.isFriendGuide()) {
            return;
        }
        int guideDropNo = 1;
        int turnPlayer = roomData.getTurnPlayer();
        WasheNoticeHelper noticeHelper = new WasheNoticeHelper(roomData.getModeId()); // 避免其他方法同步下去
        boolean isPlayer1 = isPlayer1(roomData, matchData.getKey());
        // 对话0
        WasheOperateResult result1 = new WasheOperateResult(eWasheOperateType.GuideStep, true, turnPlayer);
        result1.addShow(WasheFirstGameLogic.getClientShow(eWasheEffectType.GuideStep, 0));
        WasheProto.WasheOperateResultMsg.Builder msg1 = WashePb2.parseWasheOperateResultMsg(result1, roomData, matchData.getKey());

        // 移动1
        WasheOperateResult result2 = WasheGameLogic.operateMove(config, roomData, isPlayer1, WasheFirstGameLogic.getMoveParam(1), guideDropNo++, roomData.getRound());
        handleMoveResult(isPlayer1, roomData, result2, noticeHelper);
        WasheFirstGameLogic.replaceFirstEffect(result2.getShowList(), eWasheEffectType.ExchangeBlock, eWasheEffectType.GuideOperateMove);
        WasheProto.WasheOperateResultMsg.Builder msg2 = WashePb2.parseWasheOperateResultMsg(result2, roomData, matchData.getKey());

        // 对话1、2
        WasheOperateResult result21 = new WasheOperateResult(eWasheOperateType.GuideStep, true, turnPlayer);
        result21.addShow(WasheFirstGameLogic.getClientShow(eWasheEffectType.GuideStep, 1));
        result21.addShow(WasheFirstGameLogic.getClientShow(eWasheEffectType.GuideStep, 2));
        WasheProto.WasheOperateResultMsg.Builder msg21 = WashePb2.parseWasheOperateResultMsg(result21, roomData, matchData.getKey());

        // 移动2
        WasheOperateResult result3 = WasheGameLogic.operateMove(config, roomData, isPlayer1, WasheFirstGameLogic.getMoveParam(2), guideDropNo++, roomData.getRound());
        handleMoveResult(isPlayer1, roomData, result3, noticeHelper);
        WasheFirstGameLogic.replaceFirstEffect(result3.getShowList(), eWasheEffectType.ExchangeBlock, eWasheEffectType.GuideOperateMove);
        WasheProto.WasheOperateResultMsg.Builder msg3 = WashePb2.parseWasheOperateResultMsg(result3, roomData, matchData.getKey());

        // 对话3
        WasheOperateResult result31 = new WasheOperateResult(eWasheOperateType.GuideStep, true, turnPlayer);
        result31.addShow(WasheFirstGameLogic.getClientShow(eWasheEffectType.GuideStep, 3));
        WasheProto.WasheOperateResultMsg.Builder msg31 = WashePb2.parseWasheOperateResultMsg(result31, roomData, matchData.getKey());

        // 移动3
        WasheOperateResult result4 = WasheGameLogic.operateMove(config, roomData, isPlayer1, WasheFirstGameLogic.getMoveParam(3), guideDropNo++, roomData.getRound());
        handleMoveResult(isPlayer1, roomData, result4, noticeHelper);
        WasheFirstGameLogic.replaceFirstEffect(result4.getShowList(), eWasheEffectType.ExchangeBlock, eWasheEffectType.GuideOperateMove);
        WasheProto.WasheOperateResultMsg.Builder msg4 = WashePb2.parseWasheOperateResultMsg(result4, roomData, matchData.getKey());

        // 对话4 技能对话0
        WasheOperateResult result41 = new WasheOperateResult(eWasheOperateType.GuideStep, true, turnPlayer);
        result41.addShow(WasheFirstGameLogic.getClientShow(eWasheEffectType.GuideStep, 4));
        result41.addShow(WasheFirstGameLogic.getClientShow(eWasheEffectType.GuideOperateCard, 0));
        WasheProto.WasheOperateResultMsg.Builder msg41 = WashePb2.parseWasheOperateResultMsg(result41, roomData, matchData.getKey());

        // 技能1
        WasheOperateResult result5 = WasheGameLogic.operateUseCard(config, roomData, isPlayer1, WasheFirstGameLogic.getCardParam1(matchData.getCardId()), guideDropNo++, roomData.getRound());
        handleUseCardResult(isPlayer1, roomData, result5, noticeHelper);
        WasheProto.WasheOperateResultMsg.Builder msg5 = WashePb2.parseWasheOperateResultMsg(result5, roomData, matchData.getKey());

        // 对话5、6、7
        WasheOperateResult result51 = new WasheOperateResult(eWasheOperateType.GuideStep, true, turnPlayer);
        result51.addShow(WasheFirstGameLogic.getClientShow(eWasheEffectType.GuideStep, 5));
        result51.addShow(WasheFirstGameLogic.getClientShow(eWasheEffectType.GuideStep, 6));
        result51.addShow(WasheFirstGameLogic.getClientShow(eWasheEffectType.GuideStep, 7));
        WasheProto.WasheOperateResultMsg.Builder msg51 = WashePb2.parseWasheOperateResultMsg(result51, roomData, matchData.getKey());

        // 重置下数据
        WasheUserGameData turnPlayerData = turnPlayer == 1 ? roomData.getPlayer1() : roomData.getPlayer2();
        turnPlayerData.setStep(modeConfig.getMaxStep());
        turnPlayerData.setScore(0);
        roomData.getGameData().setBlockList(WasheFirstGameLogic.getBlockList(false, isPlayer1));
        WasheProto.WasheOperateResultMsg.Builder msg6 = WasheProto.WasheOperateResultMsg.newBuilder()
                .setSkillId(-1).setSide(roomData.getTurnPlayer()).setGameData(WashePb2.parseWasheRoomDataMsg(roomData, roomData.getTurnPlayer()));
        // 返回
        noticeHelper = new WasheNoticeHelper(roomData.getModeId());
        sendNotice(WasheProto.WasheSyncNoticeRespMsg.newBuilder().setType(eWasheOperateType.GuideStep.getValue()).setOperate(msg1), matchData, roomData.getModeId(), noticeHelper);
        sendNotice(WasheProto.WasheSyncNoticeRespMsg.newBuilder().setType(eWasheOperateType.GuideStep.getValue()).setOperate(msg2), matchData, roomData.getModeId(), noticeHelper);
        sendNotice(WasheProto.WasheSyncNoticeRespMsg.newBuilder().setType(eWasheOperateType.GuideStep.getValue()).setOperate(msg21), matchData, roomData.getModeId(), noticeHelper);
        sendNotice(WasheProto.WasheSyncNoticeRespMsg.newBuilder().setType(eWasheOperateType.GuideStep.getValue()).setOperate(msg3), matchData, roomData.getModeId(), noticeHelper);
        sendNotice(WasheProto.WasheSyncNoticeRespMsg.newBuilder().setType(eWasheOperateType.GuideStep.getValue()).setOperate(msg31), matchData, roomData.getModeId(), noticeHelper);
        sendNotice(WasheProto.WasheSyncNoticeRespMsg.newBuilder().setType(eWasheOperateType.GuideStep.getValue()).setOperate(msg4), matchData, roomData.getModeId(), noticeHelper);
        sendNotice(WasheProto.WasheSyncNoticeRespMsg.newBuilder().setType(eWasheOperateType.GuideStep.getValue()).setOperate(msg41), matchData, roomData.getModeId(), noticeHelper);
        sendNotice(WasheProto.WasheSyncNoticeRespMsg.newBuilder().setType(eWasheOperateType.GuideStep.getValue()).setOperate(msg5), matchData, roomData.getModeId(), noticeHelper);
        sendNotice(WasheProto.WasheSyncNoticeRespMsg.newBuilder().setType(eWasheOperateType.GuideStep.getValue()).setOperate(msg51), matchData, roomData.getModeId(), noticeHelper);
        sendNotice(WasheProto.WasheSyncNoticeRespMsg.newBuilder().setType(eWasheOperateType.GuideStep.getValue()).setOperate(msg6), matchData, roomData.getModeId(), noticeHelper);
        sendNoticeList(noticeHelper);
    }

    private void matchPlayerByRank(int modeId, Map<Integer, List<WasheUserMatchData>> rankUserListMap) {
        Map<Integer, WasheRankConfig> rankConfigMap = config.getRankConfigMap();

        // 【判断哪些段位可以主动匹配】
        List<Integer> canMatchList = new ArrayList<>();
        List<Integer> maxExtendList = new ArrayList<>();
        for (int rankId = 1; rankId <= rankConfigMap.size(); rankId++) {
            List<WasheUserMatchData> curRankUserList = rankUserListMap.get(rankId);
            if (curRankUserList.isEmpty()) {
                continue;
            }

            // 按匹配时间慢慢扩大匹配范围
            long minMatchBeginTime = Long.MAX_VALUE;
            for (WasheUserMatchData matchData : curRankUserList) {
                minMatchBeginTime = Math.min(matchData.getBeginMatchTime(), minMatchBeginTime);
            }
            int maxExtendTimes = config.getMatchExtendTimes(System.currentTimeMillis() - minMatchBeginTime);
            int minRank = rankId - maxExtendTimes;
            int maxRank = rankId + maxExtendTimes;

            // 扩大范围后不能超出配置范围
            List<Integer> matchRangeList = rankConfigMap.get(rankId).getMatchRangeList();
            minRank = Math.max(minRank, matchRangeList.get(0));
            maxRank = Math.min(maxRank, matchRangeList.get(1));

            // 统计范围内人数
            int count = 0;
            for (int i = minRank; i <= maxRank; i++) {
                count += rankUserListMap.get(i).size();
            }

            // 判断人数够不够
            if (count >= config.getMatchNeedPlayerNum(rankId)) {
                canMatchList.add(rankId);
                maxExtendList.add(maxExtendTimes);
            }
        }

        // 【按段位小到大匹配】
        for (int i = 0; i < canMatchList.size(); i++) {
            int rankId = canMatchList.get(i);
            int maxExtendTimes = maxExtendList.get(i);

            // 拿需要匹配的人
            List<WasheUserMatchData> list = new ArrayList<>(rankUserListMap.get(rankId));

            // 拿被匹配的人（包含自己）
            List<WasheUserMatchData> otherList = new ArrayList<>(rankUserListMap.get(rankId));
            // 人数不够需要扩大范围
            List<Integer> matchRangeList = rankConfigMap.get(rankId).getMatchRangeList();
            for (int extendTimes = 0; extendTimes < maxExtendTimes; extendTimes++) {
                if (otherList.size() >= config.getMatchNeedPlayerNum(rankId)) {
                    break;
                }

                boolean outOfRange = true;
                // 向左扩大一个段位
                int tmpRank1 = rankId - extendTimes - 1;
                if (matchRangeList.get(0) <= tmpRank1 && tmpRank1 <= matchRangeList.get(1)) {
                    otherList.addAll(rankUserListMap.get(tmpRank1));
                    outOfRange = false;
                }
                // 向右扩大一个段位
                int tmpRank2 = rankId + extendTimes + 1;
                if (matchRangeList.get(0) <= tmpRank2 && tmpRank2 <= matchRangeList.get(1)) {
                    otherList.addAll(rankUserListMap.get(tmpRank2));
                    outOfRange = false;
                }
                // 是否超范围
                if (outOfRange) {
                    break;
                }
            }

            // 打乱人员
            Collections.shuffle(list);
            Collections.shuffle(otherList);

            // 开始匹配
            for (WasheUserMatchData player1 : list) {
                if (otherList.size() < 2) {
                    break;
                }

                // 取出被匹配的玩家
                if (!otherList.remove(player1)) {
                    continue; // 已经被匹配走了
                }
                WasheUserMatchData player2 = otherList.remove(0);


                // 源list移除掉避免后面再被匹配
                rankUserListMap.get(config.getWasheRankConfig(player1.getScore(), player1.isNewHand()).getRankId()).remove(player1);
                rankUserListMap.get(config.getWasheRankConfig(player2.getScore(), player2.isNewHand()).getRankId()).remove(player2);
                // 匹配完成
                handleMatchPlayer(modeId, player1, player2);
            }
        }
    }

    private void handleMatchPlayer(int modeId, WasheUserMatchData player1, WasheUserMatchData player2) {
        matchingUserMap.remove(player1.getKey());
        matchingUserMap.remove(player2.getKey());

        int randomSceneId = randomSceneId(player1.getScore(), player2.getScore());

        eWasheModeType modeType = eWasheModeType.getType(modeId);
        if(modeType == eWasheModeType.AreaPk){
            //盘面机制  随机一种
            ChannelWashPkActivity pkActivity = ChannelWashePkMgr.getInTimeActivityInfo(player1.getChannelId(), player1.getServerId());
            if(pkActivity != null){
                List<Integer> list = pkActivity.getSceneData().getSceneList();
                if(list.size() > 0) {
                    Random random = new Random();
                    randomSceneId = list.get(random.nextInt(list.size()));
                }
            }
        }

        if(modeType != eWasheModeType.AreaPk) {
            if (player1.isNewHand() || player2.isNewHand()) {
                modeType = eWasheModeType.NewHandPvp;
            }
        }
        if (modeType == null) {
            modeType = eWasheModeType.Pvp;
        }

        WasheRoomData roomData = createRoom(modeType, randomSceneId);
        synchronized (roomData) {
            joinRoom(roomData, player1);
            joinRoom(roomData, player2);
            putRoomToMap(roomData);
            startGame(roomData);
        }

        // 预计匹配时间修改
        if (!player1.isAi() && !player2.isAi()) {
            long costTime1 = System.currentTimeMillis() - player1.getBeginMatchTime();
            addMatchCostTime(costTime1);
            long costTime2 = System.currentTimeMillis() - player2.getBeginMatchTime();
            addMatchCostTime(costTime2);
        }
    }

    private int match(WasheUserMatchData matchData) {
        synchronized (lock) {
            // 正在匹配中 || 正在房间中
            if (matchingUserMap.containsKey(matchData.getKey()) || roomUserMap.containsKey(matchData.getKey())) {
                return getStatusErrorCode(matchData.getKey());
            }
            // 开始匹配
            matchData.setBeginMatchTime(System.currentTimeMillis());
            matchingUserMap.put(matchData.getKey(), matchData);
            return 0;
        }
    }

    private List<WasheUserMatchData> getShowOpponentList(String selfKey) {
        List<WasheUserMatchData> list = new ArrayList<>();
        for (WasheUserMatchData data : matchingUserMap.values()) {
            // 跳过自己
            if (Objects.equals(data.getKey(), selfKey)) {
                continue;
            }
            // 添加展示的玩家
            list.add(data);
            if (list.size() >= config.getShowOpponentNum()) {
                break;
            }
        }

        // 人数不够就加点假数据
        if (list.size() < config.getShowOpponentNum()) {
            Optional<WasheUserMatchData> first = matchingUserMap.values().stream().findFirst();
            if (first.isPresent()) {
                WasheUserMatchData matchData = first.get();
                while (list.size() < config.getShowOpponentNum()) {
                    WasheUserMatchData npcMatchData = createNpcMatchData(matchData.getCardId(), matchData.getSkills(), matchData.getScore(), matchData.isNewHand());
                    if(npcMatchData == null){
                        continue;
                    }
                    list.add(npcMatchData);
                }
            }
        }

        return list;
    }

    private void addMatchCostTime(long costTime) {
        synchronized (matchTimeList) {
            matchTimeList.add(costTime);
            if (matchTimeList.size() > config.getMatchTimeParam()) {
                matchTimeList.removeFirst();
            }
        }
    }

    private long getAvgMatchTime() {
        if (matchTimeList.size() < config.getMatchTimeParam()) {
            return config.getShowMaxMatchTime();
        }
        LinkedList<Long> list = new LinkedList<>(matchTimeList);
        long avgTime = list.stream().mapToLong(Long::longValue).sum() / list.size();
        return Math.min(config.getShowMaxMatchTime(), avgTime);
    }

    private WasheUserMatchData createNpcMatchData(int cardId, List<Integer> skillList, long score, boolean newHand) {
        WasheRankConfig rankConfig = config.getWasheRankConfig(score, newHand);
        if(rankConfig == null){
            ChannelWasheMgr.getLogger().info("cardId {}, score {}, newHand {}, create Npc null", cardId, score, newHand);
            return null;
        }
        // 生成饰品
        List<Integer> decorationList = new ArrayList<>();
        while (decorationList.size() < 6) {
            decorationList.add(0);
        }
        int headId;
        int bodyId;
        List<Integer> npcHeadList = config.getNpcHeadList();
        List<Integer> npcBodyList = config.getNpcBodyList();
        synchronized (needLockRandom) {
            headId = npcHeadList.get(needLockRandom.nextInt(npcHeadList.size()));
            bodyId = npcBodyList.get(needLockRandom.nextInt(npcBodyList.size()));
        }
        decorationList.set(0, headId);
        decorationList.set(4, bodyId);
        // 生成名称
        int nameId;
        synchronized (needLockRandom) {
            nameId = needLockRandom.nextInt(config.getNpcNameNum()) + 1;
        }
        String nickname = config.getNpcNamePrefix() + nameId;
        // 组装baseInfo
        UserBaseInfo info = new UserBaseInfo();
        info.setDecoration(StringUtils.listToString(decorationList, DECORATION_SEPARATOR));
        info.setNickName(nickname);
        // 生成卡片
        Map<Integer, WasheCardConfig> cardConfigMap = config.getCardConfigMap();
        Set<Integer> aiCardSet = config.getAiCardSet();
        int cardQua = cardConfigMap.get(cardId).getQuality();
        Map<Integer, List<Integer>> quaIdLMap = new HashMap<>(); // 符合条件的卡片都拿出来
        int maxAiCardQua = score >= config.getAiRedCardLimit() ? 5 : 4;
        for (WasheCardConfig cardConfig : cardConfigMap.values()) {
            if (aiCardSet.contains(cardConfig.getCardId())) {
                int tmpQua = cardConfig.getQuality();
                if (cardQua - 1 <= tmpQua && tmpQua <= cardQua + 1 && tmpQua <= maxAiCardQua) {
                    quaIdLMap.putIfAbsent(tmpQua, new ArrayList<>());
                    quaIdLMap.get(tmpQua).add(cardConfig.getCardId());
                }
            }
        }
        List<Integer> tmpWeightList = config.getAiCardQuaWeightList(rankConfig.getRankId());
        List<Integer> weightList = new ArrayList<>(); // 各品质对应权重
        List<Integer> quaList = new ArrayList<>();
        for (int i = 0; i <= 2; i++) {
            int tmpQua = cardQua - 1 + i;
            if (quaIdLMap.containsKey(tmpQua)) {
                weightList.add(tmpWeightList.get(i));
                quaList.add(tmpQua);
            }
        }
        int quaIndex;
        synchronized (needLockRandom) {
            quaIndex = RandomHelper.getRandomIndexByWeight(weightList, needLockRandom);
        }
        int chooseQua = quaList.get(quaIndex);
        List<Integer> cardList = quaIdLMap.get(chooseQua); // 该品质所有卡片
        int chooseCardIndex;
        synchronized (needLockRandom) {
            chooseCardIndex = RandomHelper.getRandomIndexByWeight(cardList, needLockRandom);
        }
        int chooseCardId = cardList.get(chooseCardIndex);

        // AI只支持2种技能
        List<Integer> skills = new ArrayList<>();
        skills.add(eWasheSkillType.Skill_1001.getValue());
        skills.add(eWasheSkillType.Skill_1002.getValue());

        // 其余数据
        WasheUserMatchData matchData = new WasheUserMatchData();
        matchData.setBaseInfo(info);
        matchData.setCardId(chooseCardId);
        matchData.setSkills(new ArrayList<>(skills));
        matchData.setAi(true);
        matchData.setScore(score);
        matchData.setNewHand(newHand);

        matchData.setEffectId(0);
        matchData.setBoardSkinId(0);
        matchData.setAnimationId(0);
        return matchData;
    }

    private boolean cancelMatch(String key) {
        synchronized (lock) {
            // 已经在房间里了
            if (roomUserMap.containsKey(key)) {
                return false;
            }
            matchingUserMap.remove(key);
            return true;
        }
    }

    private WasheUserMatchData parseWasheUserMatchData(WasheUserKey userKey, WasheProto.ChannelWasheMatchReqMsg reqMsg) {
        WasheUserMatchData matchData = new WasheUserMatchData();
        matchData.setChannelId(userKey.getChannelId());
        matchData.setServerId(userKey.getServerId());
        matchData.setUserId(userKey.getUserId());
        matchData.setBaseInfo(PlayerBasePb.parseToUserBaseInfo(reqMsg.getUserBaseInfo()));
        matchData.setScore(reqMsg.getScore());
        matchData.setCardId(reqMsg.getCardId());
        matchData.setSkills(reqMsg.getSkillIdList());
        matchData.setKey(getKey(userKey));
        matchData.setServerName(ChannelXServerNameMgr.getServerNameMap().getServerName(userKey.getChannelId(), userKey.getServerId()));
        matchData.setNewHand(reqMsg.getIsGuide());
        matchData.setPlayTimes(reqMsg.getPlayTimes());
        matchData.setModeId(reqMsg.getModeId());
        matchData.setFriendGuide(reqMsg.getFriendGuide());
        matchData.setBoardSkinId(reqMsg.getBoardSkinId());
        matchData.setAnimationId(reqMsg.getAnimationId());
        matchData.setEffectId(reqMsg.getEffectId());
        return matchData;
    }


    // --------------------------- 房间 --------------------------------

    private WasheRoomData createRoom(eWasheModeType modeType, int appointSceneId) {
        WasheRoomData roomData = new WasheRoomData();
        roomData.setRoomId(roomMaxId.getAndIncrement());
        roomData.setModeId(modeType.getValue());
        roomData.setBegin(false);
        roomData.setAppointSceneId(appointSceneId);
        return roomData;
    }

    private void putRoomToMap(WasheRoomData roomData) {
        roomMap.put(roomData.getRoomId(), roomData);
    }

    private int joinRoom(WasheRoomData roomData, WasheUserMatchData matchData) {
        WasheUserGameData userGameData = WasheUserGameData.createData(config, matchData);
        joinRoomInitReady(roomData, userGameData);

        if (roomData.getPlayer1() == null) {
            roomData.setPlayer1(userGameData);
        } else if (roomData.getPlayer2() == null) {
            roomData.setPlayer2(userGameData);
        } else {
            return GameErrorCode.E_WASHE_ROOM_FULL;
        }
        matchData.setRoomId(roomData.getRoomId());
        if (!matchData.isAi()) {
            roomUserMap.put(matchData.getKey(), matchData);
        }

        // 记录玩家自己的匹配时间
        matchData.setMatchCostTime(System.currentTimeMillis() - matchData.getBeginMatchTime());

        return 0;
    }

    private int joinPeakRoom(WasheRoomData roomData, WasheUserMatchData matchData, boolean isPlayer1) {
        WasheUserGameData userGameData = WasheUserGameData.createData(config, matchData);
        joinRoomInitReady(roomData, userGameData);

        if (isPlayer1) {
            roomData.setPlayer1(userGameData);
        } else {
            roomData.setPlayer2(userGameData);
        }
        matchData.setRoomId(roomData.getRoomId());
        if (!matchData.isAi()) {
            roomUserMap.put(matchData.getKey(), matchData);
        }

        // 记录玩家自己的匹配时间
        matchData.setMatchCostTime(System.currentTimeMillis() - matchData.getBeginMatchTime());

        return 0;
    }

    private boolean isRoomPlaying(WasheRoomData roomData) {
        return roomData.isBegin() && roomData.getWinPlayer() == 0;
    }

    private void removeRoom(WasheRoomData roomData) {

        WasheUserGameData player1 = roomData.getPlayer1();
        if (player1 != null) {
            WasheUserMatchData matchData = player1.getUserMatchData();
            if (!matchData.isAi()) {
                roomUserMap.remove(matchData.getKey());
            }
        }

        WasheUserGameData player2 = roomData.getPlayer2();
        if (player2 != null) {
            WasheUserMatchData matchData = player2.getUserMatchData();
            if (!matchData.isAi()) {
                roomUserMap.remove(matchData.getKey());
            }
        }

        roomMap.remove(roomData.getRoomId());
    }

    private int player2QuitRoom(WasheRoomData roomData) {
        WasheUserGameData player2 = roomData.getPlayer2();
        if (player2 == null) {
            return 0;
        }

        roomData.setPlayer2(null);
        roomUserMap.remove(player2.getUserMatchData().getKey());

        // 同步好友
        WasheProto.WasheFriendSyncRoomRespMsg.Builder syncBuilder = WasheProto.WasheFriendSyncRoomRespMsg.newBuilder();
        syncBuilder.setType(eWasheFriendNoticeType.FriendQuitRoom.getValue());
        sendGamePacket(ClientProtocol.U_WASHE_FRIEND_SYNC_ROOM, syncBuilder, roomData.getPlayer1().getUserMatchData());

        return 0;
    }

    private void masterRemoveRoom(WasheRoomData roomData) {
        // 解散
        removeRoom(roomData);

        // 同步房间解散给好友
        WasheUserGameData player2 = roomData.getPlayer2();
        if (player2 != null) {
            WasheProto.WasheFriendSyncRoomRespMsg.Builder syncBuilder = WasheProto.WasheFriendSyncRoomRespMsg.newBuilder();
            syncBuilder.setType(eWasheFriendNoticeType.MasterRemoveRoom.getValue());
            sendGamePacket(ClientProtocol.U_WASHE_FRIEND_SYNC_ROOM, syncBuilder, player2.getUserMatchData());
        }
    }


    // --------------------------- 游戏逻辑 --------------------------------

    private int startGame(WasheRoomData roomData) {
        if (roomData.isBegin()) {
            return GameErrorCode.E_WASHE_GAME_START;
        }
        if (roomData.getPlayer1() == null || roomData.getPlayer2() == null) {
            return GameErrorCode.E_WASHE_ROOM_NOT_FULL;
        }
        boolean isFriendGuide = false;
        // 随机先手玩家
        int turnPlayer;
        if (roomData.getPlayer1().getUserMatchData().isFriendGuide()) {
            turnPlayer = 1;
            isFriendGuide = true;
        } else if (roomData.getPlayer2().getUserMatchData().isFriendGuide()) {
            turnPlayer = 2;
            isFriendGuide = true;
        } else {
            synchronized (needLockRandom) {
                turnPlayer = needLockRandom.nextInt(1000) < 500 ? 1 : 2;
            }
        }
        (turnPlayer == 1 ? roomData.getPlayer1() : roomData.getPlayer2()).setFirstOperate(true);

        // 拿回合时间
        WasheModeConfig modeConfig = config.getModeConfigMap().get(roomData.getModeId());
        long clearTime = modeConfig.getClearTime();
        long expireTime = System.currentTimeMillis() + clearTime;
        // 新手第一把多给点时间
        if (isFriendGuide) {
            expireTime = System.currentTimeMillis() + clearTime + DateHelper.MINUTE_MILLIONS * 30;
        }
        // 拿初始盘面
        List<List<WasheBlock>> initBlockList = isFriendGuide ? WasheFirstGameLogic.getBlockList(true, turnPlayer == 1) : WasheGameLogic.getInitBlockList(new Random(), config);
        WasheGameData gameData = WasheGameData.createWasheGameData(roomData.getAppointSceneId(), initBlockList);
        // 盘面机制生效
        WasheOperateResult initResult = WasheOperateResult.newSceneRefreshResult(turnPlayer);
        WasheSceneLogic.beginGameInit(config, new Random(), gameData, initResult);

        roomData.setRound(1);
        roomData.setExpireTime(expireTime);
        roomData.setTurnPlayer(turnPlayer);
        roomData.getPlayer1().setStep(modeConfig.getMaxStep());
        roomData.getPlayer2().setStep(modeConfig.getMaxStep());
        roomData.setGameData(gameData);
        roomData.setBeginTime(System.currentTimeMillis());
        roomData.setBegin(true);

        if (isFriendGuide) {
            roomData.setGuide(true);
            if (turnPlayer == 1) {
                roomData.getPlayer1().setStep(modeConfig.getMaxStep() + 1);
                roomData.getPlayer1().getCard().setCardMp(roomData.getPlayer1().getCard().getCardMaxMp() - 3);
            } else {
                roomData.getPlayer2().setStep(modeConfig.getMaxStep() + 1);
                roomData.getPlayer2().getCard().setCardMp(roomData.getPlayer2().getCard().getCardMaxMp() - 3);
            }
        }

        // 同步游戏开始
        roomData.addShowTime(10000);
        WasheUserMatchData player1 = roomData.getPlayer1().getUserMatchData();
        WasheUserMatchData player2 = roomData.getPlayer2().getUserMatchData();
        syncGameStart(roomData, player1, player2);
        syncGameStart(roomData, player2, player1);
        return 0;
    }

    private void syncGameStart(WasheRoomData roomData, WasheUserMatchData self, WasheUserMatchData opponent) {
        if (!self.isAi() && roomData.getModeId() != eWasheModeType.FriendPk.getValue() && roomData.getModeId() != eWasheModeType.Practice.getValue()) {
            // 返回客户端
            WasheProto.WasheMatchResultRespMsg.Builder builder = WasheProto.WasheMatchResultRespMsg.newBuilder();
            builder.setRet(0);
            builder.setMode(roomData.getModeId());
            builder.setSceneId(roomData.getGameData().getSceneId());
            builder.setOpponent(WashePb2.parseWasheOpponentMsg(opponent));
            sendGamePacket(GameProtocol.S_WASHE_MATCH_RESULT_FROM_CROSS, builder, self);
        }
    }

    private void timerCheckNpcOperate(WasheRoomData roomData) {
        int turn = roomData.getTurnPlayer();
        WasheUserGameData turnPlayer = turn == 1 ? roomData.getPlayer1() : roomData.getPlayer2();
        WasheUserGameData nextPlayer = turn == 1 ? roomData.getPlayer2() : roomData.getPlayer1();
        WasheUserMatchData turnUserMatchData = turnPlayer.getUserMatchData();

        // 不是AI || AI没步数
        if ((!turnUserMatchData.isAi() && !turnPlayer.isRobot()) || turnPlayer.getStep() <= 0) {
            return;
        }

        // 真人还没进来 && 第一回合 && 剩余时间还有一半以上
        long now = System.currentTimeMillis();
        if (!nextPlayer.getUserMatchData().isSeeBegin() &&
                roomData.getRound() == 1 &&
                roomData.getExpireTime() - now > config.getModeConfigMap().get(roomData.getModeId()).getClearTime() / 2) {
            return;
        }

        // 客户端正在播放动画
        if (now < roomData.getShowFinishTime()) {
            return;
        }

        // 客户端播放完动画后再确定下次操作时间
        if (turnUserMatchData.getAiOperateTime() < roomData.getShowFinishTime()) {
            turnUserMatchData.setAiOperateTime(randomAiOperateWaitTime() + roomData.getShowFinishTime());
        }

        // 未到AI操作时间
        if (now < turnUserMatchData.getAiOperateTime()) {
            return;
        }

        // AI操作
        int ret = WasheAiLogic.aiOperate(this, config, roomData, nextPlayer.getUserMatchData(), turnPlayer);
        if (ret != 0) {
            logger.error("AI operate move error! ret = {} opponentUserId = {}", ret,
                    turn == 1 ? roomData.getPlayer2().getUserMatchData().getUserId() : roomData.getPlayer1().getUserMatchData().getUserId());
        }
    }

    public void checkExpire() {

        for (WasheRoomData roomData : roomMap.values()) {
            try {

                synchronized (roomData) {

                    if (!isRoomPlaying(roomData)) {
                        continue;
                    }

                    // 检查回合切换
                    checkTurnAndGameOver(null, roomData);
                    // npc操作
                    timerCheckNpcOperate(roomData);
                }

            } catch (Exception e) {
                logger.error("error", e);
            }
        }
    }

    private int surrender(String key, boolean timesLimit) {
        WasheUserMatchData matchData = roomUserMap.get(key);
        if (matchData == null) {
            return getNoStatusErrorCode();
        }
        WasheRoomData roomData = roomMap.get(matchData.getRoomId());
        if (roomData == null) {
            return GameErrorCode.E_WASHE_ROOM_NO_EXIST;
        }
        synchronized (roomData) {
            if (!isRoomPlaying(roomData)) {
                return GameErrorCode.E_WASHE_GAME_NO_PLAYING;
            }

            if (roomData.getModeId() == eWasheModeType.JinBiaoPeak.getValue()) {
                return GameErrorCode.E_WASHE_ROOM_PEAK_CAN_NOT_SURRENDER;
            }

            if (timesLimit && roomData.getModeId() != eWasheModeType.Practice.getValue() && roomData.getModeId() != eWasheModeType.FriendPk.getValue() &&
                    roomData.getModeId() != eWasheModeType.AreaPk.getValue()) {
                return GameErrorCode.E_WASHE_DAY_SURRENDER_LIMIT;
            }

            boolean isPlayer1 = Objects.equals(roomData.getPlayer1().getUserMatchData().getKey(), key);

            roomData.setWinPlayer(isPlayer1 ? 2 : 1);
            roomData.setWinType(WasheGameOverType.SURRENDER);
            handleGameOver(null, roomData);
            return 0;
        }
    }

    private void handleGameOver(WasheNoticeHelper noticeHelper,
                                WasheRoomData roomData) {

        if (roomData.getWinPlayer() == 0 || roomData.getWinType() == 0) {
            logger.error("washe handleGameOver error! game no winner! userId1 {}, userId2 {}.",
                    roomData.getPlayer1() != null ? roomData.getPlayer1().getUserMatchData().getUserId() : -1L,
                    roomData.getPlayer2() != null ? roomData.getPlayer2().getUserMatchData().getUserId() : -1L);
            return;
        }

        List<WasheUserMatchData> playerList = new ArrayList<>();
        playerList.add(roomData.getPlayer1().getUserMatchData());
        playerList.add(roomData.getPlayer2().getUserMatchData());

        // 模式处理下
        if (roomData.getModeId() == eWasheModeType.JinBiaoScoreLow.getValue() || roomData.getModeId() == eWasheModeType.JinBiaoScoreHigh.getValue()) {
            if (config != null && !config.jinBiaoScoreInTime()) {
                // 如果不在锦标赛积分赛期间，模式id更改为pvp模式
                roomData.setModeId(eWasheModeType.Pvp.getValue());
            }
        }

        if (roomData.getModeId() != eWasheModeType.AreaPk.getValue()) {
            // 同步游戏结束
            for (WasheUserMatchData matchData : playerList) {
                if (!matchData.isAi()) {
                    WasheProto.WasheSyncNoticeRespMsg.Builder builder = WasheProto.WasheSyncNoticeRespMsg.newBuilder();
                    builder.setType(eWasheOperateType.GameOver.getValue());
                    builder.setOver(WashePb2.parseWasheGameOverMsg(roomData, matchData.getKey(), config));
                    sendNotice(builder, matchData, roomData.getModeId(), noticeHelper);
                }
            }
        }

        // 移除房间
        removeRoom(roomData);

        // 好友对战结束要再创建个房间给他们
        if (roomData.getModeId() == eWasheModeType.FriendPk.getValue()) {
            WasheRoomData room = createRoom(eWasheModeType.FriendPk, roomData.getAppointSceneId());
            if (!roomData.getPlayer1().getUserMatchData().isFriendGuide()) {
                joinRoom(room, createNewUserMatchData(roomData.getPlayer1().getUserMatchData())); // 需要createNewUserMatchData
            }
            if (!roomData.getPlayer2().getUserMatchData().isFriendGuide()) {
                joinRoom(room, createNewUserMatchData(roomData.getPlayer2().getUserMatchData())); // 需要createNewUserMatchData
            }
            putRoomToMap(room);
        }
        // 巅峰赛结束改下状态
        if (roomData.getModeId() == eWasheModeType.JinBiaoPeak.getValue()) {
            WasheJinBiaoPeakBattleData battleData = getPeakBattleByRoom(roomData.getRoomId());
            if (battleData.getBattleResult() == eWasheJinBiaoPeakBattleResultType.InBattle.getValue()) {
                // 仅当双方都不为托管时才改变状态，托管之前结果已定
                if (roomData.getWinPlayer() == 1) {
                    battleData.setBattleResult(eWasheJinBiaoPeakBattleResultType.Player1Win.getValue());
                } else if (roomData.getWinPlayer() == 2) {
                    battleData.setBattleResult(eWasheJinBiaoPeakBattleResultType.Player2Win.getValue());
                } else if (roomData.getWinPlayer() == 3) {
                    battleData.setBattleResult(eWasheJinBiaoPeakBattleResultType.NoWin.getValue());
                } else {
                    // 没有其他情况了
                }
            }
            // 记录下分数
            if (!roomData.getPlayer1().isRobot()) {
                battleData.setScore1(roomData.getPlayer1().getScore());
            } else {
                battleData.setScore1(roomData.getPlayer1().getRobotScore());
            }
            if (!roomData.getPlayer2().isRobot()) {
                battleData.setScore2(roomData.getPlayer2().getScore());
            } else {
                battleData.setScore2(roomData.getPlayer2().getRobotScore());
            }
        }

        //地域pk结束
        if (roomData.getModeId() == eWasheModeType.AreaPk.getValue()) {
            WasheUserGameData win;
            WasheUserGameData lose;
            if(roomData.getWinPlayer() == 1){
                win = roomData.getPlayer1();
                lose = roomData.getPlayer2();
            }else {
                win = roomData.getPlayer2();
                lose = roomData.getPlayer1();
            }

            WasheProto.WasheGameOverMsg.Builder over1 = WashePb2.parseWasheGameOverMsg(roomData, win.getUserMatchData().getKey(), config);
            over1.setZoneId2(lose.getUserMatchData().getZoneId());
            ChannelWashPkActivity activity = ChannelWashePkMgr.handUserScore(win, true, over1, noticeHelper, this);

            over1 = WashePb2.parseWasheGameOverMsg(roomData, lose.getUserMatchData().getKey(), config);
            over1.setZoneId2(win.getUserMatchData().getZoneId());
            ChannelWashePkMgr.handUserScore(lose, false, over1, noticeHelper, this);

            if(activity != null){
                activity.changeUserRank(win.getUserMatchData().getUserId(), true);
                activity.changeUserRank(lose.getUserMatchData().getUserId(), false);
            }
        }
    }

    public WasheJinBiaoPeakBattleData getPeakBattleByRoom(long roomId) {
        synchronized (peakRoomDataMap) {
            for (List<WasheJinBiaoPeakBattleData> battleDataList : peakRoomDataMap.values()) {
                for (WasheJinBiaoPeakBattleData battleData : battleDataList) {
                    if (battleData.getRoomData() != null && battleData.getRoomData().getRoomId() == roomId) {
                        return battleData;
                    }
                }
            }
        }

        return null;
    }

    private void handleTurnChange(WasheNoticeHelper noticeHelper, WasheRoomData roomData, boolean roundChange, long now, WasheUserGameData turn, WasheUserGameData next, WasheModeConfig modeConfig) {

        int nextTurn = roomData.getTurnPlayer() == 1 ? 2 : 1;

        // 准备数据，方便消息一起发
        List<WasheUserMatchData> list = new ArrayList<>();
        list.add(roomData.getPlayer1().getUserMatchData());
        list.add(roomData.getPlayer2().getUserMatchData());
        WasheNoticeHelper tmpNoticeHelper = noticeHelper;
        if (tmpNoticeHelper == null) {
            tmpNoticeHelper = new WasheNoticeHelper(roomData.getModeId());
        }

        // 当前玩家清除清除
        turn.setStep(0);
        turn.getCard().turnReset();
        roomData.getGameData().setScene13Count(0);
        roomData.getGameData().setScene04Count(0);
        roomData.getGameData().setScene08Count(0);
        roomData.getGameData().setScene07Count(0);

        // 清空表现数据
        roomData.getShowTimeList().clear();
        roomData.setShowFinishTime(now);
        // 房间数据修改
        roomData.setExpireTime(now + modeConfig.getClearTime());
        roomData.setTurnPlayer(nextTurn);
        // 加表现时间
        roomData.addShowTime(5000);
        // 如果对方掉线，则从头开始计时
        if (next.getUserMatchData().getDisconnectTime() > 0) {
            next.getUserMatchData().setDisconnectTime(System.currentTimeMillis());
        }

        if (roundChange) {
            // 加回合
            roomData.setRound(roomData.getRound() + 1);
            // 加步数
            turn.setStep(modeConfig.getMaxStep());
            next.setStep(modeConfig.getMaxStep());
        }

        // 回合切换清空效果
        WasheOperateResult result1 = new WasheOperateResult(eWasheOperateType.TurnPlayer, false, nextTurn);
        WasheGameLogic.turnChangeResetBlock(roomData.getGameData().getBlockList(), result1);
        WasheProto.WasheSyncNoticeRespMsg.Builder lastBuilder = WasheProto.WasheSyncNoticeRespMsg.newBuilder();
        for (WasheUserMatchData matchData : list) {
            if (!matchData.isAi()) {
                lastBuilder.setType(result1.getType().getValue());
                lastBuilder.setOperate(WashePb2.parseWasheOperateResultMsg(result1, roomData, matchData.getKey()));
                sendNotice(lastBuilder, matchData, roomData.getModeId(), tmpNoticeHelper);
            }
        }

        // 回合切换操作
        WasheOperateResult result2 = WasheOperateResult.newSceneRefreshResult(nextTurn);
        WasheSceneLogic.turnChangeRefresh(config, new Random(), roomData.getGameData(), result2);
        for (WasheUserMatchData matchData : list) {
            if (!matchData.isAi()) {
                WasheProto.WasheSyncNoticeRespMsg.Builder nextBuilder = WasheProto.WasheSyncNoticeRespMsg.newBuilder();
                nextBuilder.setType(result2.getType().getValue());
                nextBuilder.setOperate(WashePb2.parseWasheOperateResultMsg(result2, roomData, matchData.getKey()));
                sendNotice(nextBuilder, matchData, roomData.getModeId(), tmpNoticeHelper);
            }
        }

        // 一起发消息
        if (noticeHelper == null) {
            sendNoticeList(tmpNoticeHelper);
        }
    }

    private int sendExpression(String key, int expressionId) {
        WasheUserMatchData matchData = roomUserMap.get(key);
        if (matchData == null) {
            return getNoStatusErrorCode();
        }
        WasheRoomData roomData = roomMap.get(matchData.getRoomId());
        if (roomData == null) {
            return GameErrorCode.E_WASHE_ROOM_NO_EXIST;
        }

        synchronized (roomData) {
            if (!roomData.isBegin()) {
                return GameErrorCode.E_WASHE_GAME_NO_START;
            }

            boolean isPlayer1 = Objects.equals(roomData.getPlayer1().getUserMatchData().getKey(), key);
            WasheUserGameData self = isPlayer1 ? roomData.getPlayer1() : roomData.getPlayer2();
            WasheUserGameData opponent = isPlayer1 ? roomData.getPlayer2() : roomData.getPlayer1();

            // 设置表情发送时间
            long now = System.currentTimeMillis();
            if (now - self.getSendExpressionTime() < config.getExpressionInterval()) {
                return GameErrorCode.E_WASHE_EXPRESSION_FREQUENTLY;
            }
            self.setSendExpressionTime(now);

            // 发送表情
            if (!opponent.getUserMatchData().isAi()) {
                WasheProto.WasheSyncNoticeRespMsg.Builder builder = WasheProto.WasheSyncNoticeRespMsg.newBuilder();
                builder.setType(eWasheOperateType.SendExpression.getValue());
                builder.setExpressionId(expressionId);
                sendNotice(builder, opponent.getUserMatchData(), roomData.getModeId(), null);
            }

            // 返回
            int code = roomData.getModeId() == eWasheModeType.Practice.getValue() || roomData.getModeId() == eWasheModeType.FriendPk.getValue() ?
                    ClientProtocol.U_WASHE_OPERATE : GameProtocol.S_WASHE_SEND_EXPRESSION_FROM_CROSS;
            WasheProto.WasheOperateRespMsg.Builder builder = WasheProto.WasheOperateRespMsg.newBuilder();
            builder.setRet(0);
            sendGamePacket(code, builder, self.getUserMatchData());
            return 0;
        }
    }

    private int operateUseCard(String key, WasheUseSkillParam skillParam) {
        WasheUserMatchData matchData = roomUserMap.get(key);
        if (matchData == null) {
            return getNoStatusErrorCode();
        }
        WasheRoomData roomData = roomMap.get(matchData.getRoomId());
        if (roomData == null) {
            return GameErrorCode.E_WASHE_ROOM_NO_EXIST;
        }
        synchronized (roomData) {
            if (!roomData.isBegin()) {
                return GameErrorCode.E_WASHE_GAME_NO_START;
            }
            boolean isPlayer1 = isPlayer1(roomData, key);
            if (!turnSelf(roomData, isPlayer1)) {
                return GameErrorCode.E_WASHE_NO_TURN_SELF;
            }

            // 补充点参数
            skillParam.setSide(isPlayer1 ? 1 : 2);

            WasheOperateResult result = WasheGameLogic.operateUseCard(config, roomData, isPlayer1, skillParam, -1, roomData.getRound());
            if (result.getRet() != 0) {
                return result.getRet();
            }
            handleUseCardResult(isPlayer1, roomData, result, null);

            // 日志
            ChannelLogWasheLogic.addLogWasheOperate(LogWasheOperate.create(roomData, isPlayer1, eWasheLogType.UseCard, skillParam.getSkillId()));
            return 0;
        }
    }

    private int operateUseSkill(String key, WasheUseSkillParam skillParam) {
        WasheUserMatchData matchData = roomUserMap.get(key);
        if (matchData == null) {
            return getNoStatusErrorCode();
        }
        WasheRoomData roomData = roomMap.get(matchData.getRoomId());
        if (roomData == null) {
            return GameErrorCode.E_WASHE_ROOM_NO_EXIST;
        }
        synchronized (roomData) {
            if (!roomData.isBegin()) {
                return GameErrorCode.E_WASHE_GAME_NO_START;
            }
            boolean isPlayer1 = isPlayer1(roomData, key);
            if (!turnSelf(roomData, isPlayer1)) {
                return GameErrorCode.E_WASHE_NO_TURN_SELF;
            }

            WasheOperateResult result = WasheGameLogic.operateUseSkill(config, roomData, isPlayer1, skillParam, -1, roomData.getRound());
            if (result.getRet() != 0) {
                return result.getRet();
            }
            handleUseSkillResult(isPlayer1, roomData, result, null);

            // 日志
            ChannelLogWasheLogic.addLogWasheOperate(LogWasheOperate.create(roomData, isPlayer1, eWasheLogType.UseSkill, skillParam.getSkillId()));
            return 0;
        }
    }

    private int operateMove(String key, WasheMoveParam moveParam) {
        WasheUserMatchData matchData = roomUserMap.get(key);
        if (matchData == null) {
            return getNoStatusErrorCode();
        }
        WasheRoomData roomData = roomMap.get(matchData.getRoomId());
        if (roomData == null) {
            return GameErrorCode.E_WASHE_ROOM_NO_EXIST;
        }
        synchronized (roomData) {
            if (!roomData.isBegin()) {
                return GameErrorCode.E_WASHE_GAME_NO_START;
            }
            boolean isPlayer1 = isPlayer1(roomData, key);
            if (!turnSelf(roomData, isPlayer1)) {
                return GameErrorCode.E_WASHE_NO_TURN_SELF;
            }
            WasheOperateResult result = WasheGameLogic.operateMove(config, roomData, isPlayer1, moveParam, -1, roomData.getRound());
            if (result.getRet() != 0) {
                return result.getRet();
            }
            handleMoveResult(isPlayer1, roomData, result, null);

            // 日志
            ChannelLogWasheLogic.addLogWasheOperate(LogWasheOperate.create(roomData, isPlayer1, eWasheLogType.MoveBlock, 0));
            return 0;
        }
    }

    public int operateNpcMove(WasheRoomData roomData, WasheMoveParam moveParam, WasheUserGameData turnPlayer) {
        boolean isPlayer1 = roomData.getPlayer1() == turnPlayer;
        if (!turnSelf(roomData, isPlayer1)) {
            return GameErrorCode.E_WASHE_NO_TURN_SELF;
        }

        WasheOperateResult result = WasheGameLogic.operateMove(config, roomData, isPlayer1, moveParam, -1, roomData.getRound());
        if (result.getRet() != 0) {
            return result.getRet();
        }
        handleMoveResult(isPlayer1, roomData, result, null);

        // 日志
        ChannelLogWasheLogic.addLogWasheOperate(LogWasheOperate.create(roomData, isPlayer1, eWasheLogType.MoveBlock, 0));
        return 0;
    }

    public int operateNpcUseCard(WasheRoomData roomData, WasheUseSkillParam skillParam, WasheUserGameData turnPlayer) {
        boolean isPlayer1 = roomData.getPlayer1() == turnPlayer;
        if (!turnSelf(roomData, isPlayer1)) {
            return GameErrorCode.E_WASHE_NO_TURN_SELF;
        }

        WasheOperateResult result = WasheGameLogic.operateUseCard(config, roomData, isPlayer1, skillParam, -1, roomData.getRound());
        if (result.getRet() != 0) {
            return result.getRet();
        }
        handleUseCardResult(isPlayer1, roomData, result, null);

        // 日志
        ChannelLogWasheLogic.addLogWasheOperate(LogWasheOperate.create(roomData, isPlayer1, eWasheLogType.UseCard, skillParam.getSkillId()));
        return 0;
    }

    public int operateNpcUseSkill(WasheRoomData roomData, WasheUseSkillParam skillParam, WasheUserGameData turnPlayer) {
        boolean isPlayer1 = roomData.getPlayer1() == turnPlayer;
        if (!turnSelf(roomData, isPlayer1)) {
            return GameErrorCode.E_WASHE_NO_TURN_SELF;
        }

        WasheOperateResult result = WasheGameLogic.operateUseSkill(config, roomData, isPlayer1, skillParam, -1, roomData.getRound());
        if (result.getRet() != 0) {
            return result.getRet();
        }
        handleUseSkillResult(isPlayer1, roomData, result, null);

        // 日志
        ChannelLogWasheLogic.addLogWasheOperate(LogWasheOperate.create(roomData, isPlayer1, eWasheLogType.UseSkill, skillParam.getSkillId()));
        return 0;
    }

    private boolean turnSelf(WasheRoomData roomData, boolean isPlayer1) {
        if ((isPlayer1 ? 1 : 2) != roomData.getTurnPlayer()) {
            return false; // 不是轮到自己
        }
        WasheUserGameData userGameData = isPlayer1 ? roomData.getPlayer1() : roomData.getPlayer2();
        if (userGameData.getStep() <= 0) {
            return false; // 自己回合但是步数用完了（过会儿定时器就切换过去了）
        }
        return true;
    }

    private boolean isPlayer1(WasheRoomData roomData, String key) {
        WasheUserGameData player1 = roomData.getPlayer1();
        if (player1 == null) {
            return false;
        }
        return Objects.equals(player1.getUserMatchData().getKey(), key);
    }

    private boolean turnSelf(WasheRoomData roomData, String key) {
        String key1 = roomData.getPlayer1().getUserMatchData().getKey();
        boolean isPlayer1 = Objects.equals(key1, key);
        return turnSelf(roomData, isPlayer1);
    }

    private void handleMoveResult(boolean isPlayer1, WasheRoomData roomData, WasheOperateResult result, WasheNoticeHelper noticeHelper) {
        WasheModeConfig modeConfig = config.getModeConfigMap().get(roomData.getModeId());
        WasheUserGameData operatePlayer = isPlayer1 ? roomData.getPlayer1() : roomData.getPlayer2();
        int mpColor = isPlayer1 ? eWasheColorType.Red.getValue() : eWasheColorType.Blue.getValue();

        // 统计结果
        int mpCount = result.getScene07AddMp();
        long addScore = 0;
        int addStep = result.getBombAddStepStatus() == 1 ? 1 : 0;
        for (WasheOperateShow show : result.getShowList()) {
            for (WasheOperateRoundResult roundResult : show.getRoundResultList()) {
                for (WasheOperateStageResult stageResult : roundResult.getStageResultList()) {
                    for (WasheBlock block : stageResult.getEliminateList()) {
                        if (block.getBlockType().getBigType() != eWasheBlockType.BIG_TYPE_FRAME) {
                            if (block.getColor() == mpColor) {
                                mpCount += 1;
                                block.setSp(1);
                            }
                            block.setScore(calcBlockScore(block));
                            addScore += block.getScore();
                        }
                    }
                    for (WasheBlock block : stageResult.getAddStepBlockList()) {
                        if (block.getBlockType() == eWasheBlockType.DianShanLeiMing) {
                            addStep += WasheSceneLogic.scene_09_getAddStepNum(config);
                        }
                    }
                }
            }
        }

        // 修改数据
        operatePlayer.reduceStep();
        operatePlayer.addScore(addScore);
        operatePlayer.getCard().addMp(mpCount);
        roomData.setGameData(result.getFinalData());
        operatePlayer.addStep(addStep, modeConfig);

        // 同步操作结果
        roomData.addShowTime(20000);
        List<WasheUserMatchData> list = new ArrayList<>();
        list.add(roomData.getPlayer1().getUserMatchData());
        list.add(roomData.getPlayer2().getUserMatchData());
        for (WasheUserMatchData matchData : list) {
            if (!matchData.isAi()) {
                WasheProto.WasheSyncNoticeRespMsg.Builder builder = WasheProto.WasheSyncNoticeRespMsg.newBuilder();
                builder.setType(eWasheOperateType.Move.getValue());
                builder.setOperate(WashePb2.parseWasheOperateResultMsg(result, roomData, matchData.getKey()));
                sendNotice(builder, matchData, roomData.getModeId(), noticeHelper);
            }
        }
    }

    private void handleUseCardResult(boolean isPlayer1, WasheRoomData roomData, WasheOperateResult result, WasheNoticeHelper noticeHelper) {
        WasheModeConfig modeConfig = config.getModeConfigMap().get(roomData.getModeId());
        WasheUserGameData operatePlayer = isPlayer1 ? roomData.getPlayer1() : roomData.getPlayer2();

        // 统计结果
        int mpCount = result.getScene07AddMp();
        long addScore = 0;
        int addStep = result.getBombAddStepStatus() == 1 ? 1 : 0;
        for (WasheOperateShow show : result.getShowList()) {
            for (WasheOperateRoundResult roundResult : show.getRoundResultList()) {
                for (WasheOperateStageResult stageResult : roundResult.getStageResultList()) {
                    for (WasheBlock block : stageResult.getEliminateList()) {
                        if (block.getBlockType().getBigType() != eWasheBlockType.BIG_TYPE_FRAME) {
                            block.setScore(calcBlockScore(block));
                            addScore += block.getScore();
                        }
                    }
                    for (WasheBlock block : stageResult.getAddStepBlockList()) {
                        if (block.getBlockType() == eWasheBlockType.DianShanLeiMing) {
                            addStep += WasheSceneLogic.scene_09_getAddStepNum(config);
                        }
                    }
                }
            }
        }

        // 修改数据
        WasheUserGameCard card = operatePlayer.getCard();
        operatePlayer.addScore(addScore);
        card.setCardMp(0);
        operatePlayer.getCard().addMp(mpCount);
        roomData.setGameData(result.getFinalData());
        operatePlayer.addStep(addStep, modeConfig);

        // 同步操作结果
        roomData.addShowTime(config.getCardConfigMap().get(card.getCardId()).getMaxTime());
        List<WasheUserMatchData> list = new ArrayList<>();
        list.add(roomData.getPlayer1().getUserMatchData());
        list.add(roomData.getPlayer2().getUserMatchData());
        for (WasheUserMatchData matchData : list) {
            if (!matchData.isAi()) {
                WasheProto.WasheSyncNoticeRespMsg.Builder builder = WasheProto.WasheSyncNoticeRespMsg.newBuilder();
                builder.setType(eWasheOperateType.UseCard.getValue());
                builder.setOperate(WashePb2.parseWasheOperateResultMsg(result, roomData, matchData.getKey()));
                sendNotice(builder, matchData, roomData.getModeId(), noticeHelper);
            }
        }
    }

    private void handleUseSkillResult(boolean isPlayer1, WasheRoomData roomData, WasheOperateResult result, WasheNoticeHelper noticeHelper) {
        WasheModeConfig modeConfig = config.getModeConfigMap().get(roomData.getModeId());
        WasheUserGameData operatePlayer = isPlayer1 ? roomData.getPlayer1() : roomData.getPlayer2();
        int mpColor = isPlayer1 ? eWasheColorType.Red.getValue() : eWasheColorType.Blue.getValue();

        // 统计结果
        int mpCount = result.getScene07AddMp();
        long addScore = 0;
        int addStep = result.getBombAddStepStatus() == 1 ? 1 : 0;
        boolean useSkill_1003 = result.getSkillId() == eWasheSkillType.Skill_1003.getValue();
        for (WasheOperateShow show : result.getShowList()) {
            for (WasheOperateRoundResult roundResult : show.getRoundResultList()) {
                for (WasheOperateStageResult stageResult : roundResult.getStageResultList()) {
                    for (WasheBlock block : stageResult.getEliminateList()) {
                        if (block.getBlockType().getBigType() != eWasheBlockType.BIG_TYPE_FRAME) {
                            if (useSkill_1003 || block.getColor() == mpColor) {
                                mpCount += 1;
                                block.setSp(1);
                            }
                            block.setScore(calcBlockScore(block));
                            addScore += block.getScore();
                        }
                    }
                    for (WasheBlock block : stageResult.getAddStepBlockList()) {
                        if (block.getBlockType() == eWasheBlockType.DianShanLeiMing) {
                            addStep += WasheSceneLogic.scene_09_getAddStepNum(config);
                        }
                    }
                }
            }
        }

        // 修改数据
        int skillId = result.getSkillId();
        for (WasheUserGameSkill skill : operatePlayer.getSkillList()) {
            if (skill.getSkillId() == skillId) {
                skill.setTimes(skill.getTimes() - 1);
            }
        }
        operatePlayer.addScore(addScore);
        operatePlayer.getCard().addMp(mpCount);
        roomData.setGameData(result.getFinalData());
        // 先扣步数再加步数
        if (useSkill_1003) {
            operatePlayer.reduceStep();
        }
        operatePlayer.addStep(addStep, modeConfig);

        // 同步操作结果
        roomData.addShowTime(config.getSkillConfigMap().get(skillId).getMaxTime());
        List<WasheUserMatchData> list = new ArrayList<>();
        list.add(roomData.getPlayer1().getUserMatchData());
        list.add(roomData.getPlayer2().getUserMatchData());
        for (WasheUserMatchData matchData : list) {
            if (!matchData.isAi()) {
                WasheProto.WasheSyncNoticeRespMsg.Builder builder = WasheProto.WasheSyncNoticeRespMsg.newBuilder();
                builder.setType(eWasheOperateType.UseSkill.getValue());
                builder.setOperate(WashePb2.parseWasheOperateResultMsg(result, roomData, matchData.getKey()));
                sendNotice(builder, matchData, roomData.getModeId(), noticeHelper);
            }
        }
    }

    private void checkTurnAndGameOver(WasheNoticeHelper noticeHelper, WasheRoomData roomData) {

        if (!isRoomPlaying(roomData)) {
            return;
        }

        boolean turnChange = false;
        boolean roundChange = false;
        boolean gameOver = false;

        long now = System.currentTimeMillis();
        WasheUserGameData turn = roomData.getTurnPlayer() == 1 ? roomData.getPlayer1() : roomData.getPlayer2();
        WasheUserGameData next = roomData.getTurnPlayer() == 1 ? roomData.getPlayer2() : roomData.getPlayer1();

        // 【检查对手切换】
        WasheModeConfig modeConfig = config.getModeConfigMap().get(roomData.getModeId());
        WasheUserMatchData turnUserMatchData = turn.getUserMatchData();
        if (turn.isRobot() || turnUserMatchData.getDisconnectTime() <= 0) { // 操作方没掉线时才能切换
            if (modeConfig.getClearTime() != 0 && now > roomData.getExpireTime()
                    || turn.getStep() <= 0 && now > roomData.getShowFinishTime()) {
                turnChange = true; // 超时 || 步数用光且表现结束
            }
        }

        if (turnChange) {
            // 【检查回合切换】
            if (next.getStep() <= 0) {
                roundChange = true; // 切换对手，对手也没步数
            }
        }

        if (roundChange) {
            // 【检查游戏正常结束】
            if (roomData.getRound() >= modeConfig.getMaxRound() && turn.getScore() != next.getScore()) {
                gameOver = true; // 回合切换时达到最大回合，且双方分数不同
                if (roomData.getPeakWinPlayer() != 0) {
                    roomData.setWinPlayer(roomData.getPeakWinPlayer());
                    roomData.setWinType(WasheGameOverType.DISCONNECT);
                } else {
                    roomData.setWinPlayer(roomData.getPlayer1().getScore() > roomData.getPlayer2().getScore() ? 1 : 2);
                    roomData.setWinType(WasheGameOverType.NORMAL);
                }
            }
            // 巅峰赛存在平局，巅峰赛达到最大加赛回合直接结束
            if (roomData.getModeId() == eWasheModeType.JinBiaoPeak.getValue() && turn.getScore() == next.getScore() && roomData.getRound() >= modeConfig.getMaxRound() + config.getJinBiaoTopAddRoundNum()) {
                gameOver = true; // 回合切换时达到最大回合
                if (roomData.getPeakWinPlayer() != 0) {
                    roomData.setWinPlayer(roomData.getPeakWinPlayer());
                    roomData.setWinType(WasheGameOverType.DISCONNECT);
                } else {
                    // 平局
                    roomData.setWinPlayer(3);
                    roomData.setWinType(WasheGameOverType.NOWIN);
                }
            }
        }

        // 【检查游戏掉线结束】
        if (!gameOver) {
            long disconnectTime = turnUserMatchData.getDisconnectTime();
            if (disconnectTime > 0) {
                long failTime = disconnectTime + config.getDisconnFailTime(turnUserMatchData.getDisconnTimes());
                if (System.currentTimeMillis() > failTime) {
                    // 巅峰赛特殊处理下
                    if (roomData.getModeId() == eWasheModeType.JinBiaoPeak.getValue()) {
                        if (next.isRobot()) {
                            // 对方已经托管了，直接结束游戏
                            gameOver = true;
                            roomData.setWinPlayer(roomData.getPeakWinPlayer());
                            roomData.setWinType(WasheGameOverType.DISCONNECT);
                        } else {
                            // 处理下小玉托管,托管对局结果已固定,但不会结束游戏
                            boolean isPlayer1 = roomData.getPlayer1().getUserMatchData().getUserId() == turn.getUserMatchData().getUserId();
                            // 小玉接管
                            handlePeakRobot(roomData, isPlayer1, turn.getUserMatchData().getUserId(), turn.getUserMatchData().getSkills());
                            roomData.setPeakWinPlayer(roomData.getTurnPlayer() == 1 ? 2 : 1);
                        }
                    } else {
                        gameOver = true; // 当前玩家掉线超过一定时间
                        roomData.setWinPlayer(roomData.getTurnPlayer() == 1 ? 2 : 1);
                        roomData.setWinType(WasheGameOverType.DISCONNECT);
                    }
                }
            }
        }

        // -------------- 处理结果 --------------

        if (gameOver) {
            // 处理游戏结果
            handleGameOver(noticeHelper, roomData);
        } else if (turnChange) {
            // 处理对手/回合切换
            handleTurnChange(noticeHelper, roomData, roundChange, now, turn, next, modeConfig);
            // 检查掉线消息
            checkSendDisconnect(noticeHelper, roomData);
        }
    }

    private void checkSendDisconnect(WasheNoticeHelper noticeHelper, WasheRoomData roomData) {
        int turn = roomData.getTurnPlayer();
        WasheUserGameData turnPlayer = turn == 1 ? roomData.getPlayer1() : roomData.getPlayer2();
        WasheUserGameData nextPlayer = turn == 1 ? roomData.getPlayer2() : roomData.getPlayer1();

        WasheUserMatchData turnPlayerUserMatchData = turnPlayer.getUserMatchData();
        long disconnectTime = turnPlayerUserMatchData.getDisconnectTime();
        if (disconnectTime > 0 && !nextPlayer.getUserMatchData().isAi() && !turnPlayer.isRobot()) {
            long failTime = disconnectTime + config.getDisconnFailTime(turnPlayerUserMatchData.getDisconnTimes()); // 掉线失败时间
            WasheProto.WasheSyncNoticeRespMsg.Builder builder = WasheProto.WasheSyncNoticeRespMsg.newBuilder();
            builder.setType(eWasheOperateType.OpponentDisconnect.getValue());
            builder.setDropLineWaitTime(failTime);
            sendNotice(builder, nextPlayer.getUserMatchData(), roomData.getModeId(), noticeHelper);
        }
    }

    private int showFinish(String key, long showNo) {
        WasheUserMatchData matchData = roomUserMap.get(key);
        if (matchData == null) {
            return getNoStatusErrorCode();
        }
        WasheRoomData roomData = roomMap.get(matchData.getRoomId());
        if (roomData == null) {
            return GameErrorCode.E_WASHE_ROOM_NO_EXIST;
        }

        synchronized (roomData) {

            if (!isRoomPlaying(roomData)) {
                return GameErrorCode.E_WASHE_GAME_NO_START;
            }
            boolean isPlayer1 = isPlayer1(roomData, key);

            boolean isFirst = (roomData.getModeId() == eWasheModeType.Guide.getValue() || matchData.isFriendGuide()) && !matchData.isSeeBegin();
            boolean isSeeBegin = matchData.isSeeBegin();

            // 先修改首次状态
            if (isFirst && roomData.getModeId()!=eWasheModeType.FriendPk.getValue()) roomData.setModeId(eWasheModeType.NewHandPvp.getValue()); // 避免引导中断卡死
            if (!isSeeBegin) {
                matchData.setSeeBegin(true); // 避免重复开入场回合切换

                // 日志
                ChannelLogWasheLogic.addLogWasheOperate(LogWasheOperate.create(roomData, isPlayer1, eWasheLogType.EnterRoom, 0));
            }

            // 处理首次事件
            if (isFirst) {
                handleGuide_First(matchData, roomData, config.getModeConfigMap().get(roomData.getModeId())); // 开始引导
            } else if (!isSeeBegin) {
                WasheProto.WasheSyncNoticeRespMsg.Builder builder = WasheProto.WasheSyncNoticeRespMsg.newBuilder();
                builder.setType(eWasheOperateType.TurnPlayer.getValue());
                builder.setOperate(WasheProto.WasheOperateResultMsg.newBuilder()
                        .setSide(roomData.getTurnPlayer())
                        .setSkillId(0)
                        .setGameData(WashePb2.parseWasheRoomDataMsg(roomData, matchData.getKey())));
                sendNotice(builder, matchData, roomData.getModeId(), null);
            }

            // 判断动画速度按谁的来
            int turn = isPlayer1 ? 1 : 2;
            WasheUserMatchData opponent = isPlayer1 ? roomData.getPlayer2().getUserMatchData() : roomData.getPlayer1().getUserMatchData();
            if (turn != roomData.getTurnPlayer() && !opponent.isAi()) {
                return GameErrorCode.E_WASHE_NO_TURN_SELF;
            }

            // 动画耗时修改
            boolean needSync = roomData.showFinish(showNo);
            if (needSync) {
                WasheNoticeHelper helper = new WasheNoticeHelper(roomData.getModeId());
                List<WasheUserMatchData> list = new ArrayList<>();
                list.add(roomData.getPlayer1().getUserMatchData());
                list.add(roomData.getPlayer2().getUserMatchData());
                for (WasheUserMatchData userMatchData : list) {
                    if (!userMatchData.isAi()) {
                        WasheProto.WasheSyncNoticeRespMsg.Builder builder = WasheProto.WasheSyncNoticeRespMsg.newBuilder();
                        builder.setType(eWasheOperateType.SyncExpireTime.getValue());
                        builder.setExpireTime(roomData.getExpireTime());
                        sendNotice(builder, userMatchData, roomData.getModeId(), helper); // 表现完成同步超时时间
                    }
                }
                checkTurnAndGameOver(helper, roomData); // 同步回合变化
                sendNoticeList(helper); // 发送
            }
        }
        return 0;
    }

    private int calcBlockScore(WasheBlock block) {
        // 基础分
        int score = block.getBlockType().getBigType() != eWasheBlockType.BIG_TYPE_FRAME ? config.getBlockScore() : 0;
        // 额外加分
        int multi = 1;
        for (WasheBlockEffect effect : block.getEffectList()) {
            if (effect.getEffectType() == eWasheBlockEffectType.Skill_7) {
                score += (effect.getEffectValue() - 1);
            } else if (effect.getEffectType() == eWasheBlockEffectType.AddScore) {
                score += effect.getEffectValue();
            } else if (effect.getEffectType() == eWasheBlockEffectType.WuCaiBinFen) {
                score += (effect.getEffectValue() - 1);
            } else if (effect.getEffectType() == eWasheBlockEffectType.MultiScore_Hide) {
                multi *= effect.getEffectValue();
            } else if (effect.getEffectType() == eWasheBlockEffectType.AddScore_Hide) {
                multi += effect.getEffectValue();
            }
        }
        return score * multi;
    }

    private long randomAiOperateWaitTime() {
        List<Integer> aiOperateTimeList = config.getAiOperateTimeList();
        int waitTime;
        synchronized (needLockRandom) {
            waitTime = needLockRandom.nextInt(aiOperateTimeList.get(1) - aiOperateTimeList.get(0) + 1) + // 这里记得加1，不然配置固定1000毫秒，0就报错了
                    aiOperateTimeList.get(0);
        }
        return waitTime;
    }

    // 根据返回值和表现编号，判断是否需要让客户端刷新数据
    private WasheProto.WasheRoomDataMsg.Builder checkRetRoomData(int ret, String userKey, long showNo) {
        if (ret == 0 || showNo < 0) {
            return null;
        }
        WasheUserMatchData matchData = roomUserMap.get(userKey);
        if (matchData != null) {
            WasheRoomData roomData = roomMap.get(matchData.getRoomId());
            if (roomData != null && roomData.isBegin() && showNo < roomData.getShowNo()) {
                return WashePb2.parseWasheRoomDataMsg(roomData, userKey);
            }
        }
        return null;
    }

    private void joinRoomInitReady(WasheRoomData roomData, WasheUserGameData willJoinUser) {
        // 好友对战玩家2进房间默认是未准备状态
        if (roomData.getModeId() == eWasheModeType.FriendPk.getValue() && roomData.getPlayer1() != null && roomData.getPlayer2() == null) {
            willJoinUser.setReady(false);
        }
    }


    // --------------------------- 盘面机制 --------------------------------

    public void checkRefreshScene(boolean force) {
        synchronized (sceneData) {
            long now = System.currentTimeMillis();
            if (!force) {
                if (DateHelper.getZeroTimeStamp(now) <= sceneData.getRefreshTime()) {
                    return;
                }
            }

            ConcurrentHashMap<Integer, Integer> sceneWeightMap = sceneData.getSceneWeightMap();

            // 拿可选盘面机制和权重
            List<Integer> sceneIdList = new ArrayList<>();
            List<Integer> sceneWeightList = new ArrayList<>();
            for (WasheSceneConfig sceneConfig : config.getSceneConfigMap().values()) {
                if (sceneConfig.isOpen() && eWasheSceneType.getEnumByType(sceneConfig.getSceneId()) != null) {
                    // Id
                    sceneIdList.add(sceneConfig.getSceneId());
                    // Weight
                    sceneWeightList.add(config.getSceneBaseWeight() + sceneWeightMap.getOrDefault(sceneConfig.getSceneId(), 0));
                }
            }

            // 随机
            Random random = new Random();
            List<Integer> randomSceneList = new ArrayList<>();
            for (int i = 0; i < config.getDaySceneNum() && sceneIdList.size() > 0; i++) {
                int index = RandomHelper.getRandomIndexByWeight(sceneWeightList, random);
                int sceneId = sceneIdList.remove(index);
                sceneWeightList.remove(index);
                randomSceneList.add(sceneId);
            }
            if (randomSceneList.isEmpty()) {
                randomSceneList.add(eWasheSceneType.Empty.getType()); // 容错：至少一个盘面机制
            }

            // 设置
            sceneData.setSceneList(randomSceneList);
            sceneData.setRefreshTime(now);

            // 计算下次权重加成
            for (WasheSceneConfig sceneConfig : config.getSceneConfigMap().values()) {
                int sceneId = sceneConfig.getSceneId();
                if (sceneConfig.isOpen() && eWasheSceneType.getEnumByType(sceneId) != null) {
                    if (randomSceneList.contains(sceneId)) {
                        sceneWeightMap.put(sceneId, 0);
                    } else {
                        sceneWeightMap.put(sceneId, sceneWeightMap.getOrDefault(sceneId, 0) + config.getSceneAddiWeight());
                    }
                }
            }
            sceneData.setUpdateOption();
        }

        // 同步到活动内渠道
        for (int channelId : config.getActivityChannelList()) {
            WasheProto.ChannelWasheSyncDaySceneRespMsg.Builder builder = WasheProto.ChannelWasheSyncDaySceneRespMsg.newBuilder();
            builder.addAllSceneId(sceneData.getSceneList());
            ChannelMessageHelper.sendAllGamePacket(GameProtocol.S_WASHE_SYNC_DAY_SCENE, builder, channelId);
        }
    }

    private int randomSceneId(long score1, long score2) {
        boolean canScene = score1 >= config.getUnlockSceneScore() && score2 >= config.getUnlockSceneScore();
        if (!canScene) {
            return eWasheSceneType.Empty.getType();
        }

        List<Integer> sceneList = sceneData.getSceneList();
        if (sceneList.isEmpty()) {
            return eWasheSceneType.Empty.getType();
        }

        // 开始随机
        int index;
        synchronized (needLockRandom) {
            index = needLockRandom.nextInt(sceneList.size());
        }
        return sceneList.get(index);
    }

    private void checkSceneDelete() {
        List<Integer> sceneList = new ArrayList<>(sceneData.getSceneList());
        for (Integer sceneId : sceneList) {
            WasheSceneConfig sceneConfig = config.getSceneConfigMap().get(sceneId);
            if (sceneConfig != null && sceneConfig.isOpen() && eWasheSceneType.getEnumByType(sceneId) != null) {
                continue;
            } else {
                checkRefreshScene(true);
                logger.error("-------------------- 今日盘面机制有问题，已重新刷新，问题ID {} ----------", sceneId);
                break;
            }
        }
    }

    // --------------------------- 好友对战 --------------------------------

    private boolean friendCheckAllReady(WasheRoomData roomData) {
        if (roomData.getModeId() == eWasheModeType.FriendPk.getValue()
                && roomData.getPlayer1() != null && roomData.getPlayer1().isReady()
                && roomData.getPlayer2() != null && roomData.getPlayer2().isReady()) {
            return true;
        }
        return false;
    }

    private int friendOpenRoom(WasheUserMatchData matchData, int sceneId) {

        // 盘面机制是否开放
        WasheSceneConfig sceneConfig = config.getSceneConfigMap().get(sceneId);
        if (sceneConfig == null || !sceneConfig.isOpen() || eWasheSceneType.getEnumByType(sceneId) == null) {
            return GameErrorCode.E_WASHE_SCENE_NO_OPEN;
        }

        synchronized (lock) {
            // 正在匹配中 || 正在房间中
            if (matchingUserMap.containsKey(matchData.getKey()) || roomUserMap.containsKey(matchData.getKey())) {
                return getStatusErrorCode(matchData.getKey());
            }
            // 创建房间
            WasheRoomData room = createRoom(eWasheModeType.FriendPk, sceneId);
            synchronized (room) {
                joinRoom(room, matchData);
                putRoomToMap(room);
            }
            // 返回
            WasheProto.WasheFriendCreateRoomRespMsg.Builder builder = WasheProto.WasheFriendCreateRoomRespMsg.newBuilder();
            builder.setRet(0);
            builder.setRoomInfo(WashePb2.parseWasheFriendRoomInfoMsg(room, isPlayer1(room, matchData.getKey())));
            sendGamePacket(ClientProtocol.U_WASHE_FRIEND_CREATE_ROOM, builder, matchData);
            return 0;
        }
    }

    private int friendJoinRoom(WasheUserMatchData matchData, long roomId) {
        synchronized (lock) {
            // 正在匹配中 || 正在房间中
            if (matchingUserMap.containsKey(matchData.getKey()) || roomUserMap.containsKey(matchData.getKey())) {
                return getStatusErrorCode(matchData.getKey());
            }
            // 获取房间
            WasheRoomData roomData = roomMap.get(roomId);
            if (roomData == null) {
                return GameErrorCode.E_WASHE_ROOM_NO_EXIST;
            }
            if (roomData.getModeId() != eWasheModeType.FriendPk.getValue()) {
                return GameErrorCode.E_WASHE_ROOM_NO_EXIST;
            }
            // 加入房间
            synchronized (roomData) {
                int ret = joinRoom(roomData, matchData);
                if (ret != 0) {
                    return ret;
                }
            }

            // 返回
            WasheProto.WasheFriendJoinRoomRespMsg.Builder builder = WasheProto.WasheFriendJoinRoomRespMsg.newBuilder();
            builder.setRet(0);
            builder.setRoomInfo(WashePb2.parseWasheFriendRoomInfoMsg(roomData, isPlayer1(roomData, matchData.getKey())));
            sendGamePacket(ClientProtocol.U_WASHE_FRIEND_JOIN_ROOM, builder, matchData);

            // 同步好友
            WasheProto.WasheFriendSyncRoomRespMsg.Builder syncBuilder = WasheProto.WasheFriendSyncRoomRespMsg.newBuilder();
            syncBuilder.setType(eWasheFriendNoticeType.FriendJoinRoom.getValue());
            syncBuilder.setFriend(WashePb2.parseWasheOpponentMsg(roomData.getPlayer2().getUserMatchData()));
            syncBuilder.setArg(roomData.getPlayer2().getUserMatchData().getCardId());
            sendGamePacket(ClientProtocol.U_WASHE_FRIEND_SYNC_ROOM, syncBuilder, roomData.getPlayer1().getUserMatchData());

            return 0;
        }
    }

    private int friendQuitRoom(String key) {

        WasheUserMatchData matchData = roomUserMap.get(key);
        if (matchData == null) {
            return 0;
        }
        WasheRoomData roomData = roomMap.get(matchData.getRoomId());
        if (roomData == null) {
            return 0;
        }
        if (roomData.getModeId() != eWasheModeType.FriendPk.getValue()) {
            return GameErrorCode.E_WASHE_ROOM_IS_NOT_FRIEND_PK;
        }

        synchronized (roomData) {
            if (roomData.isBegin()) {
                return GameErrorCode.E_WASHE_GAME_START;
            }

            boolean isPlayer1 = isPlayer1(roomData, key);
            if (isPlayer1) {
                masterRemoveRoom(roomData);
            } else {
                // 离开
                int ret = player2QuitRoom(roomData);
                if (ret != 0) {
                    return ret;
                }
            }
        }
        return 0;
    }

    private int friendStepOut(String key) {
        WasheUserMatchData matchData = roomUserMap.get(key);
        if (matchData == null) {
            return getNoStatusErrorCode();
        }
        WasheRoomData roomData = roomMap.get(matchData.getRoomId());
        if (roomData == null) {
            return GameErrorCode.E_WASHE_ROOM_NO_EXIST;
        }
        if (roomData.getModeId() != eWasheModeType.FriendPk.getValue()) {
            return GameErrorCode.E_WASHE_ROOM_IS_NOT_FRIEND_PK;
        }
        synchronized (roomData) {
            if (roomData.isBegin()) {
                return GameErrorCode.E_WASHE_GAME_START;
            }
            boolean isPlayer1 = isPlayer1(roomData, key);
            WasheUserGameData userGameData = isPlayer1 ? roomData.getPlayer1() : roomData.getPlayer2();
            userGameData.setStepOutTime(System.currentTimeMillis());

            // 同步好友
            WasheUserGameData opponent = isPlayer1 ? roomData.getPlayer2() : roomData.getPlayer1();
            if (opponent != null) {
                WasheProto.WasheFriendSyncRoomRespMsg.Builder syncBuilder = WasheProto.WasheFriendSyncRoomRespMsg.newBuilder();
                syncBuilder.setType(eWasheFriendNoticeType.MasterStepOut.getValue());
                sendGamePacket(ClientProtocol.U_WASHE_FRIEND_SYNC_ROOM, syncBuilder, opponent.getUserMatchData());
            }
            return 0;
        }
    }

    private int friendReturnRoom(String key) {
        WasheUserMatchData matchData = roomUserMap.get(key);
        if (matchData == null) {
            return getNoStatusErrorCode();
        }
        WasheRoomData roomData = roomMap.get(matchData.getRoomId());
        if (roomData == null) {
            return GameErrorCode.E_WASHE_ROOM_NO_EXIST;
        }
        if (roomData.getModeId() != eWasheModeType.FriendPk.getValue()) {
            return GameErrorCode.E_WASHE_ROOM_IS_NOT_FRIEND_PK;
        }
        if (roomData.isBegin()) {
            return GameErrorCode.E_WASHE_ROOM_NO_EXIST;
        }
        boolean isPlayer1;
        synchronized (roomData) {
            isPlayer1 = isPlayer1(roomData, key);
            WasheUserGameData userGameData = isPlayer1 ? roomData.getPlayer1() : roomData.getPlayer2();
            userGameData.setStepOutTime(0);
        }
        // 返回
        WasheProto.WasheFriendEnterMyRoomRespMsg.Builder builder = WasheProto.WasheFriendEnterMyRoomRespMsg.newBuilder();
        builder.setRoomInfo(WashePb2.parseWasheFriendRoomInfoMsg(roomData, isPlayer1));
        sendGamePacket(ClientProtocol.U_WASHE_FRIEND_ENTER_MY_ROOM, builder, matchData);
        // 同步好友
        WasheUserGameData opponent = isPlayer1 ? roomData.getPlayer2() : roomData.getPlayer1();
        if (opponent != null) {
            WasheProto.WasheFriendSyncRoomRespMsg.Builder syncBuilder = WasheProto.WasheFriendSyncRoomRespMsg.newBuilder();
            syncBuilder.setType(eWasheFriendNoticeType.MasterReturnRoom.getValue());
            sendGamePacket(ClientProtocol.U_WASHE_FRIEND_SYNC_ROOM, syncBuilder, opponent.getUserMatchData());
        }
        return 0;
    }

    private int friendStartGame(String key) {
        WasheUserMatchData matchData = roomUserMap.get(key);
        if (matchData == null) {
            return getNoStatusErrorCode();
        }
        WasheRoomData roomData = roomMap.get(matchData.getRoomId());
        if (roomData == null) {
            return GameErrorCode.E_WASHE_ROOM_NO_EXIST;
        }
        if (roomData.getModeId() != eWasheModeType.FriendPk.getValue()) {
            return GameErrorCode.E_WASHE_ROOM_IS_NOT_FRIEND_PK;
        }
        synchronized (lock) {
            if (roomData.isBegin()) {
                return GameErrorCode.E_WASHE_GAME_START;
            }
            if (roomData.getPlayer1() == null || roomData.getPlayer2() == null) {
                return GameErrorCode.E_WASHE_ROOM_NOT_FULL;
            }
            if (!friendCheckAllReady(roomData)) {
                return GameErrorCode.E_WASHE_OPPONENT_NO_READY;
            }
            boolean isPlayer1 = isPlayer1(roomData, key);
            if (!isPlayer1) {
                return GameErrorCode.E_WASHE_IS_NOT_ROOM_MASTER;
            }
            if (roomData.getPlayer2().getStepOutTime() > 0) {
                return GameErrorCode.E_WASHE_OPPONENT_STEP_OUT;
            }
            if (roomData.getPlayer1().getStepOutTime() > 0) {
                roomData.getPlayer1().setStepOutTime(0); // 容错，房主都能开始游戏了，肯定在界面了
            }
            startGame(roomData);

            List<WasheUserMatchData> list = new ArrayList<>();
            list.add(roomData.getPlayer1().getUserMatchData());
            list.add(roomData.getPlayer2().getUserMatchData());

            // 同步好友
            for (WasheUserMatchData tmp : list) {
                WasheProto.WasheFriendSyncRoomRespMsg.Builder syncBuilder = WasheProto.WasheFriendSyncRoomRespMsg.newBuilder();
                syncBuilder.setType(eWasheFriendNoticeType.MasterStartGame.getValue());
                sendGamePacket(ClientProtocol.U_WASHE_FRIEND_SYNC_ROOM, syncBuilder, tmp);
            }

            // 让游戏服卡开始ping
            for (WasheUserMatchData tmp : list) {
                WasheProto.ChannelWasheStatusRespMsg.Builder statusBuilder = WasheProto.ChannelWasheStatusRespMsg.newBuilder();
                statusBuilder.setType(1);
                statusBuilder.setModeId(matchData.getModeId());
                sendGamePacket(GameProtocol.S_WASHE_STATUS_FROM_CROSS, statusBuilder, tmp);
            }

            return 0;
        }
    }

    private int friendQuickChat(String key, int chatIndex) {
        WasheUserMatchData matchData = roomUserMap.get(key);
        if (matchData == null) {
            return getNoStatusErrorCode();
        }
        WasheRoomData roomData = roomMap.get(matchData.getRoomId());
        if (roomData == null) {
            return GameErrorCode.E_WASHE_ROOM_NO_EXIST;
        }
        if (roomData.getModeId() != eWasheModeType.FriendPk.getValue()) {
            return GameErrorCode.E_WASHE_ROOM_IS_NOT_FRIEND_PK;
        }

        boolean player1 = isPlayer1(roomData, key);
        WasheUserGameData opponent = player1 ? roomData.getPlayer2() : roomData.getPlayer1();
        if (opponent != null) {
            WasheProto.WasheFriendSyncRoomRespMsg.Builder syncBuilder = WasheProto.WasheFriendSyncRoomRespMsg.newBuilder();
            syncBuilder.setType(eWasheFriendNoticeType.SendQuickChat.getValue());
            syncBuilder.setArg(chatIndex);
            sendGamePacket(ClientProtocol.U_WASHE_FRIEND_SYNC_ROOM, syncBuilder, opponent.getUserMatchData());
        }

        return 0;
    }

    private int friendChangeCard(String key, int cardId, List<Integer> skillIdList) {
        WasheUserMatchData matchData = roomUserMap.get(key);
        if (matchData == null) {
            return getNoStatusErrorCode();
        }
        WasheRoomData roomData = roomMap.get(matchData.getRoomId());
        if (roomData == null) {
            return GameErrorCode.E_WASHE_ROOM_NO_EXIST;
        }
        if (roomData.getModeId() != eWasheModeType.FriendPk.getValue()) {
            return GameErrorCode.E_WASHE_ROOM_IS_NOT_FRIEND_PK;
        }

        boolean isPlayer1;
        synchronized (roomData) {
            if (roomData.isBegin()) {
                return GameErrorCode.E_WASHE_GAME_START;
            }

            isPlayer1 = isPlayer1(roomData, key);
            WasheUserGameData userGameData = isPlayer1 ? roomData.getPlayer1() : roomData.getPlayer2();

            if (!isPlayer1 && userGameData.isReady()) {
                return GameErrorCode.E_WASHE_READY_CAN_NO_CHANGE_CARD;
            }

            // 换卡片
            WasheCardConfig cardConfig = config.getCardConfigMap().get(cardId);
            userGameData.getUserMatchData().setCardId(cardConfig.getCardId());
            userGameData.setCard(new WasheUserGameCard(cardConfig.getCardId(), 0, cardConfig.getCd()));
            // 换技能
            userGameData.getUserMatchData().setSkills(skillIdList);
            List<WasheUserGameSkill> skillList = new ArrayList<>();
            for (int skillId : skillIdList) {
                skillList.add(new WasheUserGameSkill(skillId, config.getSkillUseTimes()));
            }
            userGameData.setSkillList(skillList);
        }

        // 同步
        List<WasheUserGameData> list = new ArrayList<>();
        WasheUserGameData player1 = roomData.getPlayer1();
        if (player1 != null) {
            list.add(player1);
        }
        WasheUserGameData player2 = roomData.getPlayer2();
        if (player2 != null) {
            list.add(player2);
        }
        for (WasheUserGameData tmp : list) {
            WasheProto.WasheFriendSyncRoomRespMsg.Builder syncBuilder = WasheProto.WasheFriendSyncRoomRespMsg.newBuilder();
            syncBuilder.setType(isPlayer1 ? eWasheFriendNoticeType.Player1ChangeCard.getValue() : eWasheFriendNoticeType.Player2ChangeCard.getValue());
            syncBuilder.setArg(cardId);
            sendGamePacket(ClientProtocol.U_WASHE_FRIEND_SYNC_ROOM, syncBuilder, tmp.getUserMatchData());
        }

        return 0;
    }

    private int friendReady(String key, boolean ready) {
        WasheUserMatchData matchData = roomUserMap.get(key);
        if (matchData == null) {
            return getNoStatusErrorCode();
        }
        WasheRoomData roomData = roomMap.get(matchData.getRoomId());
        if (roomData == null) {
            return GameErrorCode.E_WASHE_ROOM_NO_EXIST;
        }
        if (roomData.getModeId() != eWasheModeType.FriendPk.getValue()) {
            return GameErrorCode.E_WASHE_ROOM_IS_NOT_FRIEND_PK;
        }

        boolean isPlayer1;
        synchronized (roomData) {
            if (roomData.isBegin()) {
                return GameErrorCode.E_WASHE_GAME_START;
            }

            isPlayer1 = isPlayer1(roomData, key);
            WasheUserGameData userGameData = isPlayer1 ? roomData.getPlayer1() : roomData.getPlayer2();
            userGameData.setReady(ready);
        }

        // 同步
        List<WasheUserGameData> list = new ArrayList<>();
        WasheUserGameData player1 = roomData.getPlayer1();
        if (player1 != null) {
            list.add(player1);
        }
        WasheUserGameData player2 = roomData.getPlayer2();
        if (player2 != null) {
            list.add(player2);
        }
        for (WasheUserGameData tmp : list) {
            WasheProto.WasheFriendSyncRoomRespMsg.Builder syncBuilder = WasheProto.WasheFriendSyncRoomRespMsg.newBuilder();
            syncBuilder.setType(eWasheFriendNoticeType.Player2Ready.getValue());
            syncBuilder.setArg(ready ? 1 : 0);
            sendGamePacket(ClientProtocol.U_WASHE_FRIEND_SYNC_ROOM, syncBuilder, tmp.getUserMatchData());
        }

        return 0;
    }

    private int friendChangeScene(String key, int sceneId) {
        WasheUserMatchData matchData = roomUserMap.get(key);
        if (matchData == null) {
            return getNoStatusErrorCode();
        }
        WasheRoomData roomData = roomMap.get(matchData.getRoomId());
        if (roomData == null) {
            return GameErrorCode.E_WASHE_ROOM_NO_EXIST;
        }
        if (roomData.getModeId() != eWasheModeType.FriendPk.getValue()) {
            return GameErrorCode.E_WASHE_ROOM_IS_NOT_FRIEND_PK;
        }

        // 盘面机制是否开放
        WasheSceneConfig sceneConfig = config.getSceneConfigMap().get(sceneId);
        if (sceneConfig == null || !sceneConfig.isOpen() || eWasheSceneType.getEnumByType(sceneId) == null) {
            return GameErrorCode.E_WASHE_SCENE_NO_OPEN;
        }

        synchronized (roomData) {
            if (roomData.isBegin()) {
                return GameErrorCode.E_WASHE_GAME_START;
            }

            if (!isPlayer1(roomData, key)) {
                return GameErrorCode.E_WASHE_IS_NOT_ROOM_MASTER;
            }

            roomData.setAppointSceneId(sceneId);
        }

        // 同步
        List<WasheUserGameData> list = new ArrayList<>();
        WasheUserGameData player1 = roomData.getPlayer1();
        if (player1 != null) {
            list.add(player1);
        }
        WasheUserGameData player2 = roomData.getPlayer2();
        if (player2 != null) {
            list.add(player2);
        }
        for (WasheUserGameData tmp : list) {
            WasheProto.WasheFriendSyncRoomRespMsg.Builder syncBuilder = WasheProto.WasheFriendSyncRoomRespMsg.newBuilder();
            syncBuilder.setType(eWasheFriendNoticeType.ChangeScene.getValue());
            syncBuilder.setArg(sceneId);
            sendGamePacket(ClientProtocol.U_WASHE_FRIEND_SYNC_ROOM, syncBuilder, tmp.getUserMatchData());
        }

        return 0;
    }

    private int practice(WasheUserMatchData matchData, int sceneId) {

        // 盘面机制是否开放
        WasheSceneConfig sceneConfig = config.getSceneConfigMap().get(sceneId);
        if (sceneConfig == null || !sceneConfig.isOpen() || eWasheSceneType.getEnumByType(sceneId) == null) {
            return GameErrorCode.E_WASHE_SCENE_NO_OPEN;
        }

        synchronized (lock) {
            // 正在匹配中 || 正在房间中
            if (matchingUserMap.containsKey(matchData.getKey()) || roomUserMap.containsKey(matchData.getKey())) {
                return getStatusErrorCode(matchData.getKey());
            }
            WasheUserMatchData npc = createNpcMatchData(matchData.getCardId(), matchData.getSkills(), matchData.getScore(), matchData.isNewHand());
            WasheRoomData roomData = createRoom(eWasheModeType.Practice, sceneId);
            synchronized (roomData) {
                joinRoom(roomData, matchData);
                joinRoom(roomData, npc);
                putRoomToMap(roomData);
                startGame(roomData);
            }
            return 0;
        }
    }

    private WasheUserMatchData createNewUserMatchData(WasheUserMatchData old) {
        WasheUserMatchData matchData = new WasheUserMatchData();
        matchData.setChannelId(old.getChannelId());
        matchData.setServerId(old.getServerId());
        matchData.setUserId(old.getUserId());
        matchData.setBaseInfo(old.getBaseInfo());
        matchData.setScore(old.getScore());
        matchData.setCardId(old.getCardId());
        matchData.setSkills(old.getSkills());
        matchData.setKey(old.getKey());
        matchData.setServerName(ChannelXServerNameMgr.getServerNameMap().getServerName(old.getChannelId(), old.getServerId()));
        matchData.setNewHand(old.isNewHand());
        matchData.setPlayTimes(old.getPlayTimes());
        matchData.setModeId(old.getModeId());
        matchData.setBoardSkinId(old.getBoardSkinId());
        matchData.setAnimationId(old.getAnimationId());
        matchData.setEffectId(old.getEffectId());
        return matchData;
    }

    // --------------------------- 其他 --------------------------------

    private String getKey(WasheUserKey userKey) {
        // 修改格式需同时修改getNpcKey()
        return userKey.getChannelId() + "_" + userKey.getServerId() + "_" + userKey.getUserId();
    }

    private String getNpcKey() {
        return "0_0_0";
    }

    public void removeExpireErrorRoom() {
        for (WasheRoomData roomData : roomMap.values()) {
            synchronized (roomData) {

                long now = System.currentTimeMillis();

                // 好友对战创建1小时未开战斗 || 练习模式对战超过12小时移除 || 非练习模式对战超过1小时移除
                if (roomData.getModeId() == eWasheModeType.FriendPk.getValue() & !roomData.isBegin() && now - roomData.getCreateTime() > DateHelper.HOUR_MILLIONS
                        || roomData.getModeId() == eWasheModeType.Practice.getValue() && roomData.isBegin() && now - roomData.getBeginTime() > DateHelper.HOUR_MILLIONS * 12
                        || roomData.getModeId() != eWasheModeType.Practice.getValue() && roomData.isBegin() && now - roomData.getBeginTime() > DateHelper.HOUR_MILLIONS) {

                    // 移除房间
                    removeRoom(roomData);

                    // 打个日志
                    logger.error("remove expire error room userId1 {} userId2 {}.",
                            roomData.getPlayer1() != null ? roomData.getPlayer1().getUserMatchData().getUserId() : -1L,
                            roomData.getPlayer2() != null ? roomData.getPlayer2().getUserMatchData().getUserId() : -1L);
                }
            }
        }

        // 房间号900万后重置下避免位数太高，定时器调用
        if (roomMaxId.get() > 9000000) {
            roomMaxId.set(100);
        }
    }

    public int getStatusErrorCode(String key) {
        WasheUserMatchData matchData;

        // 匹配中
        matchData = matchingUserMap.get(key);
        if (matchData != null) {
            return GameErrorCode.E_WASHE_STATUS_MATCHING;
        }

        matchData = roomUserMap.get(key);
        if (matchData != null) {
            WasheRoomData roomData = roomMap.get(matchData.getRoomId());
            if (roomData != null) {

                // 好友对战中
                if (roomData.getModeId() == eWasheModeType.FriendPk.getValue()) {
                    return GameErrorCode.E_WASHE_STATUS_FRIEND_PK;
                }
                // 练习模式中
                if (roomData.getModeId() == eWasheModeType.Practice.getValue()) {
                    return GameErrorCode.E_WASHE_STATUS_PRACTICE;
                }
                // pvp中
                return GameErrorCode.E_WASHE_STATUS_PVP;

            }

        }

        return GameErrorCode.E_WASHE_STATUS_ERROR;
    }

    private int getNoStatusErrorCode() {
        return GameErrorCode.E_WASHE_STATUS_GAME_END;
    }

    // --------------------------- 消息 --------------------------------

    private void sendGamePacket(int code, AbstractMessage.Builder<?> messageBuilder, WasheUserKey userKey) {
        ChannelMessageHelper.sendGamePacket(code, messageBuilder, userKey.getChannelId(), userKey.getServerId(), userKey.getUserId());
    }

    private void sendGamePacket(int code, AbstractMessage.Builder<?> messageBuilder, WasheUserMatchData matchData) {
        if (matchData == null || matchData.isAi()) {
            return;
        }
        ChannelMessageHelper.sendGamePacket(code, messageBuilder, matchData.getChannelId(), matchData.getServerId(), matchData.getUserId());
    }

    private void sendNoticeList(WasheNoticeHelper helper) {
        for (Map.Entry<WasheUserMatchData, List<WasheProto.WasheSyncNoticeRespMsg>> entry : helper.getKeyListMap().entrySet()) {

            WasheUserMatchData matchData = entry.getKey();
            if (matchData == null || matchData.isAi()) {
                continue;
            }

            List<WasheProto.WasheSyncNoticeRespMsg> msgList = entry.getValue();
            if (msgList.size() <= 0) {
                continue;
            }

            WasheProto.WasheBatchSyncNoticeRespMsg.Builder builder = WasheProto.WasheBatchSyncNoticeRespMsg.newBuilder();
            builder.addAllMsgList(msgList);
            builder.setModeId(helper.getModeId());
            sendGamePacket(GameProtocol.S_WASHE_NOTICE_FROM_CROSS, builder, matchData);
        }
    }

    public void sendNotice(WasheProto.WasheSyncNoticeRespMsg.Builder msg, WasheUserMatchData matchData, int modeId,
                            WasheNoticeHelper helper) {

        if (matchData == null || matchData.isAi()) {
            return;
        }

        if (helper != null) {
            helper.addNotice(msg, matchData);
            return;
        }

        WasheProto.WasheBatchSyncNoticeRespMsg.Builder builder = WasheProto.WasheBatchSyncNoticeRespMsg.newBuilder();
        builder.addMsgList(msg.build());
        builder.setModeId(modeId);
        sendGamePacket(GameProtocol.S_WASHE_NOTICE_FROM_CROSS, builder, matchData);
    }


    // --------------------------- CMD --------------------------------

    public void matchByCmd(WasheUserKey userKey, WasheProto.ChannelWasheMatchReqMsg reqMsg) {
        WasheUserMatchData matchData = parseWasheUserMatchData(userKey, reqMsg);

        int ret = 0;
        if(matchData.getModeId() == eWasheModeType.AreaPk.getValue()){
            ChannelWashPkActivity activity = ChannelWashePkMgr.getInTimeActivityInfo(userKey.getChannelId(), userKey.getServerId());
            if(activity != null){
                UserEnterName enterName = ChannelEnterNameMgr.getUserEnterName(activity.getActivityInfo().getActivityId(), userKey.getUserId());
                //未报名，或者未分组
                if(enterName == null){
                    ret = GameErrorCode.E_ENTER_NAME_NOT_IN_NAME;
                } else if(enterName.getGroupId() == 0){
                    ret = GameErrorCode.E_ENTER_NAME_NOT_IN_ACTIVITY_TIME;
                } else {
                    matchData.setZoneId(enterName.getZoneId());
                    ChannelWashePkUserData userData = activity.getChannelUserData(userKey.getUserId(), null);
                    if(userData != null){
                        matchData.setWinTimesMax(userData.getMaxWinTimes());
                        matchData.setWinTimes(userData.getWinTimes());
                        matchData.setScore(userData.getScore());
                    }
                }
            }else {
                ret = GameErrorCode.E_ACTIVITY_OVER_TIME;
            }
        }

        // 匹配
        if(ret == 0){
            ret = match(matchData);
        }

        WasheProto.WasheMatchRespMsg.Builder builder = WasheProto.WasheMatchRespMsg.newBuilder();
        builder.setRet(ret);
        if (ret == 0) {
            // 拿一下预计时间
            long matchTime = getAvgMatchTime();
            builder.setExpectMatchTime(matchTime);

            // 拿一下正在匹配的玩家
            List<WasheUserMatchData> showOpponentList = getShowOpponentList(matchData.getKey());
            for (WasheUserMatchData opponent : showOpponentList) {
                builder.addOpponent(WashePb2.parseWasheOpponentMsg(opponent));
            }

            // 让游戏服卡开始ping
            WasheProto.ChannelWasheStatusRespMsg.Builder statusBuilder = WasheProto.ChannelWasheStatusRespMsg.newBuilder();
            statusBuilder.setType(1);
            statusBuilder.setModeId(matchData.getModeId());
            sendGamePacket(GameProtocol.S_WASHE_STATUS_FROM_CROSS, statusBuilder, matchData);
        }

        sendGamePacket(ClientProtocol.U_WASHE_MATCH, builder, userKey);
    }

    public void cancelMatchByCmd(WasheUserKey userKey) {
        String key = getKey(userKey);
        boolean cancelMatch = cancelMatch(key);

        WasheProto.WasheCancelMatchRespMsg.Builder builder = WasheProto.WasheCancelMatchRespMsg.newBuilder();
        builder.setMatchSuccess(!cancelMatch);
        sendGamePacket(GameProtocol.S_WASHE_CANCEL_MATCH_FROM_CROSS, builder, userKey);
    }

    public void getRoomDataByCmd(WasheUserKey userKey) {
        String key = getKey(userKey);

        WasheRoomData roomData = null;
        WasheUserMatchData matchData = roomUserMap.get(key);
        if (matchData != null) {
            roomData = roomMap.get(matchData.getRoomId());
        }

        WasheProto.WasheGetRoomDataRespMsg.Builder builder = WasheProto.WasheGetRoomDataRespMsg.newBuilder();
        if (roomData != null) {
            builder.setRet(0);
            builder.setRoomData(WashePb2.parseWasheRoomDataMsg(roomData, key));
        } else {
            builder.setRet(getNoStatusErrorCode());
        }
        sendGamePacket(ClientProtocol.U_WASHE_GET_ROOM_DATA, builder, userKey);
    }

    public void surrenderByCmd(WasheUserKey userKey, boolean timesLimit) {
        String key = getKey(userKey);

        int ret = surrender(key, timesLimit);
        WasheProto.WasheSurrenderRespMsg.Builder builder = WasheProto.WasheSurrenderRespMsg.newBuilder();
        builder.setRet(ret);
        sendGamePacket(ClientProtocol.U_WASHE_SURRENDER, builder, userKey);
    }

    public void sendExpressionByCmd(WasheUserKey userKey, int expressionId) {
        String key = getKey(userKey);

        int ret = sendExpression(key, expressionId);
        if (ret != 0) {
            WasheProto.WasheOperateRespMsg.Builder builder = WasheProto.WasheOperateRespMsg.newBuilder();
            builder.setRet(ret);
            sendGamePacket(ClientProtocol.U_WASHE_OPERATE, builder, userKey);
        }
    }

    public void guideSuccessByCmd(WasheUserKey userKey) {
        String key = getKey(userKey);

        WasheUserMatchData matchData = roomUserMap.get(key);
        if (matchData != null) {
            WasheRoomData roomData = roomMap.get(matchData.getRoomId());
            if (roomData != null) {
                synchronized (roomData) {
                    WasheModeConfig modeConfig = config.getModeConfigMap().get(roomData.getModeId());

                    // 已开始，人机，第一回合，轮到玩家1，玩家1还是0分，进度条比配置的长1分钟
                    if (roomData.isBegin()
                            && roomData.getPlayer2().getUserMatchData().isAi()
                            && roomData.getRound() == 1
                            && roomData.getTurnPlayer() == 1
                            && roomData.getPlayer1().getScore() == 0
                            && roomData.getExpireTime() - System.currentTimeMillis() > modeConfig.getClearTime() + DateHelper.MINUTE_MILLIONS) {
                        // 修改引导的超时时间
                        roomData.setExpireTime(System.currentTimeMillis() + modeConfig.getClearTime());
                        // 同步引导超时时间
                        WasheProto.WasheSyncNoticeRespMsg.Builder builder = WasheProto.WasheSyncNoticeRespMsg.newBuilder();
                        builder.setType(eWasheOperateType.SyncExpireTime.getValue());
                        builder.setExpireTime(roomData.getExpireTime());
                        sendNotice(builder, matchData, roomData.getModeId(), null);
                    }
                    // 好友对战模式
                    if (roomData.isBegin()
                            && roomData.getModeId() == eWasheModeType.FriendPk.getValue()
                            && roomData.isGuide()) {
                        // 结束引导
                        roomData.setGuide(false);
                        // 修改引导的超时时间
                        roomData.setExpireTime(System.currentTimeMillis() + modeConfig.getClearTime());

                        // 同步
                        List<WasheUserMatchData> list = new ArrayList<>();
                        list.add(roomData.getPlayer1().getUserMatchData());
                        list.add(roomData.getPlayer2().getUserMatchData());
                        // 发条通知
                        for (WasheUserMatchData data : list) {
                            if (data != null && !data.isAi()) {
                                WasheProto.WasheSyncNoticeRespMsg.Builder builder = WasheProto.WasheSyncNoticeRespMsg.newBuilder();
                                builder.setType(eWasheOperateType.UseSkill.getValue());
                                builder.setOperate(WasheProto.WasheOperateResultMsg.newBuilder()
                                        .setSide(roomData.getTurnPlayer())
                                        .setSkillId(-1)
                                        .setGameData(WashePb2.parseWasheRoomDataMsg(roomData, data.getKey())));
                                sendNotice(builder, data, roomData.getModeId(), null);
                            }
                        }
                    }
                }
            }
        }

        WasheProto.WasheOperateRespMsg.Builder builder = WasheProto.WasheOperateRespMsg.newBuilder();
        builder.setRet(0);
        sendGamePacket(ClientProtocol.U_WASHE_OPERATE, builder, userKey);
    }

    public void useCardByCmd(WasheUserKey userKey, WasheUseSkillParam param, long showNo) {
        String key = getKey(userKey);

        int ret = operateUseCard(key, param);
        WasheProto.WasheRoomDataMsg.Builder roomBuilder = checkRetRoomData(ret, key, showNo);

        WasheProto.WasheOperateRespMsg.Builder builder = WasheProto.WasheOperateRespMsg.newBuilder();
        builder.setRet(ret);
        if (roomBuilder != null) {
            builder.setRoomData(roomBuilder);
        }
        sendGamePacket(ClientProtocol.U_WASHE_OPERATE, builder, userKey);
    }

    public void useSkillByCmd(WasheUserKey userKey, WasheUseSkillParam param, long showNo) {
        String key = getKey(userKey);

        int ret = operateUseSkill(key, param);
        WasheProto.WasheRoomDataMsg.Builder roomBuilder = checkRetRoomData(ret, key, showNo);

        WasheProto.WasheOperateRespMsg.Builder builder = WasheProto.WasheOperateRespMsg.newBuilder();
        builder.setRet(ret);
        if (roomBuilder != null) {
            builder.setRoomData(roomBuilder);
        }
        sendGamePacket(ClientProtocol.U_WASHE_OPERATE, builder, userKey);
    }

    public void moveByCmd(WasheUserKey userKey, WasheMoveParam param, long showNo) {
        String key = getKey(userKey);

        int ret = operateMove(key, param);
        WasheProto.WasheRoomDataMsg.Builder roomBuilder = checkRetRoomData(ret, key, showNo);

        WasheProto.WasheOperateRespMsg.Builder builder = WasheProto.WasheOperateRespMsg.newBuilder();
        builder.setRet(ret);
        if (roomBuilder != null) {
            builder.setRoomData(roomBuilder);
        }
        sendGamePacket(ClientProtocol.U_WASHE_OPERATE, builder, userKey);
    }

    public void showFinishByCmd(WasheUserKey userKey, long showNo) {
        String key = getKey(userKey);

        showFinish(key, showNo);
    }

    public void disconnectByCmd(WasheUserKey userKey) {
        String key = getKey(userKey);

        // 匹配的队列移除
        synchronized (lock) {
            matchingUserMap.remove(key);
        }

        // 有房间就设置掉线时间
        WasheUserMatchData matchData = roomUserMap.get(key);
        if (matchData != null) {
            WasheRoomData roomData = roomMap.get(matchData.getRoomId());
            if (roomData != null) {
                synchronized (roomData) {
                    long disconnectTime = System.currentTimeMillis() - (long) (config.getPingInterval() * config.getDisconnectPingTimes());
                    matchData.setDisconnectTime(disconnectTime);

                    // 检查是否要发送给对方
                    checkSendDisconnect(null, roomData);

                    // 日志
                    ChannelLogWasheLogic.addLogWasheOperate(LogWasheOperate.create(roomData, isPlayer1(roomData, key), eWasheLogType.Disconnect, 0));
                }
            }
        }
    }

    public void reConnectByCmd(WasheUserKey userKey) {
        String key = getKey(userKey);

        WasheUserMatchData matchData = roomUserMap.get(key);
        if (matchData == null) {
            return;
        }
        WasheRoomData roomData = roomMap.get(matchData.getRoomId());
        if (roomData == null) {
            return;
        }

        synchronized (roomData) {
            long disconnectTime = matchData.getDisconnectTime();
            if (disconnectTime > 0) {

                // 本次掉线时间
                long now = System.currentTimeMillis();
                long costTime = now - disconnectTime;

                // 修改掉线状态
                matchData.setDisconnectTime(0);

                if (roomData.isBegin()) {

                    if (turnSelf(roomData, key)) {

                        // 补掉线时间        增加的时间 = min ( 满时间 , 旧时间+掉线时间 ) - 当前时间
                        long clearTime = config.getModeConfigMap().get(roomData.getModeId()).getClearTime();
                        long addTime = Math.min(now + clearTime, roomData.getExpireTime() + costTime) - roomData.getExpireTime();
                        roomData.addExpireTime(addTime);

                        // 游戏开始10秒后才记录掉线次数，避免进来慢了
                        if (now > roomData.getBeginTime() + config.getFirstDisconnTime()) {
                            matchData.setDisconnTimes(matchData.getDisconnTimes() + 1);
                        }

                        // 同步重连状态 给对方
                        boolean isPlayer1 = Objects.equals(roomData.getPlayer1().getUserMatchData().getKey(), key);
                        WasheUserMatchData opponent = isPlayer1 ? roomData.getPlayer2().getUserMatchData() : roomData.getPlayer1().getUserMatchData();
                        if (matchData.getModeId() == eWasheModeType.JinBiaoPeak.getValue() || !opponent.isAi()) {
                            WasheProto.WasheSyncNoticeRespMsg.Builder builder = WasheProto.WasheSyncNoticeRespMsg.newBuilder();
                            builder.setType(eWasheOperateType.OpponentReConnect.getValue());
                            builder.setExpireTime(roomData.getExpireTime());
                            sendNotice(builder, opponent, roomData.getModeId(), null);
                        }
                    } else {
                        // 没有轮到自己，不算时间次数
                    }

                    // 同步重连状态 给自己
                    WasheProto.WasheSyncNoticeRespMsg.Builder builder = WasheProto.WasheSyncNoticeRespMsg.newBuilder();
                    builder.setType(eWasheOperateType.ReConnect.getValue());
                    sendNotice(builder, matchData, roomData.getModeId(), null);
                }

                // 日志
                ChannelLogWasheLogic.addLogWasheOperate(LogWasheOperate.create(roomData, isPlayer1(roomData, key), eWasheLogType.Reconnect, 0));
            }
        }

    }

    public void getPlayingGameByCmd(WasheUserKey userKey) {
        String key = getKey(userKey);

        WasheRoomData roomData = null;
        WasheUserMatchData matchData = roomUserMap.get(key);
        if (matchData != null) {
            roomData = roomMap.get(matchData.getRoomId());
        }

        WasheProto.WasheGetPlayingGameRespMsg.Builder builder = WasheProto.WasheGetPlayingGameRespMsg.newBuilder();
        if (roomData != null && isRoomPlaying(roomData)) {
            builder.setRoomData(WashePb2.parseWasheRoomDataMsg(roomData, key));
        }
        sendGamePacket(ClientProtocol.U_WASHE_GET_PLAYING_GAME, builder, userKey);
    }

    public void friendJoinRoomByCmd(WasheUserKey userKey, WasheProto.ChannelWasheMatchReqMsg reqMsg, long roomId) {
        WasheUserMatchData matchData = parseWasheUserMatchData(userKey, reqMsg);
        int ret = friendJoinRoom(matchData, roomId);
        if (ret != 0) {
            WasheProto.WasheFriendJoinRoomRespMsg.Builder builder = WasheProto.WasheFriendJoinRoomRespMsg.newBuilder();
            builder.setRet(ret);
            sendGamePacket(ClientProtocol.U_WASHE_FRIEND_JOIN_ROOM, builder, matchData);
        }
    }

    public void friendLeaveRoomByCmd(WasheUserKey userKey) {
        String key = getKey(userKey);
        int ret = friendQuitRoom(key);

        WasheProto.WasheFriendLeaveRoomRespMsg.Builder builder = WasheProto.WasheFriendLeaveRoomRespMsg.newBuilder();
        builder.setRet(ret);
        sendGamePacket(ClientProtocol.U_WASHE_FRIEND_LEAVE_ROOM, builder, userKey);
    }

    public void friendOpenRoomByCmd(WasheUserKey userKey, WasheProto.ChannelWasheMatchReqMsg reqMsg, int sceneId) {
        WasheUserMatchData matchData = parseWasheUserMatchData(userKey, reqMsg);
        int ret = friendOpenRoom(matchData, sceneId);
        if (ret != 0) {
            WasheProto.WasheFriendCreateRoomRespMsg.Builder builder = WasheProto.WasheFriendCreateRoomRespMsg.newBuilder();
            builder.setRet(ret);
            sendGamePacket(ClientProtocol.U_WASHE_FRIEND_CREATE_ROOM, builder, userKey);
        }
    }

    public void friendReturnRoomByCmd(WasheUserKey userKey) {
        int ret = friendReturnRoom(getKey(userKey));
        if (ret != 0) {
            WasheProto.WasheFriendEnterMyRoomRespMsg.Builder builder = WasheProto.WasheFriendEnterMyRoomRespMsg.newBuilder();
            sendGamePacket(ClientProtocol.U_WASHE_FRIEND_ENTER_MY_ROOM, builder, userKey);
        }
    }

    public void friendSearchRoomByCmd(WasheUserKey userKey, long roomId) {
        WasheProto.WasheFriendSearchRoomRespMsg.Builder builder = WasheProto.WasheFriendSearchRoomRespMsg.newBuilder();

        WasheRoomData roomData = roomMap.get(roomId);
        if (roomData == null || roomData.getModeId() != eWasheModeType.FriendPk.getValue()) {
            builder.setRet(GameErrorCode.E_WASHE_ROOM_NO_EXIST);
        } else {
            builder.setRet(0);
            builder.addResult(WashePb2.parseWasheFriendRoomInfoMsg(roomData, isPlayer1(roomData, getKey(userKey))));
        }

        sendGamePacket(ClientProtocol.U_WASHE_FRIEND_SEARCH_ROOM, builder, userKey);
    }

    public void friendStartGameByCmd(WasheUserKey userKey) {
        String key = getKey(userKey);
        int ret = friendStartGame(key);

        WasheProto.WasheFriendStartGameRespMsg.Builder builder = WasheProto.WasheFriendStartGameRespMsg.newBuilder();
        builder.setRet(ret);
        sendGamePacket(ClientProtocol.U_WASHE_FRIEND_START_GAME, builder, userKey);
    }

    public void friendStepOutByCmd(WasheUserKey userKey) {
        String key = getKey(userKey);
        int ret = friendStepOut(key);

        WasheProto.WasheFriendStepOutRespMsg.Builder builder = WasheProto.WasheFriendStepOutRespMsg.newBuilder();
        builder.setRet(ret);
        sendGamePacket(ClientProtocol.U_WASHE_FRIEND_STEP_OUT, builder, userKey);
    }

    public void practiceByCmd(WasheUserKey userKey, WasheProto.ChannelWasheMatchReqMsg reqMsg) {
        WasheUserMatchData matchData = parseWasheUserMatchData(userKey, reqMsg);
        int appointSceneId = reqMsg.getAppointSceneId();

        // 匹配
        int ret = practice(matchData, appointSceneId);

        WasheProto.WasheMatchRespMsg.Builder builder = WasheProto.WasheMatchRespMsg.newBuilder();
        builder.setRet(ret);
        if (ret == 0) {
            // 拿一下预计时间
            builder.setExpectMatchTime(0);

            // 拿一下正在匹配的玩家
            // 不需要了

            // 让游戏服卡开始ping
            WasheProto.ChannelWasheStatusRespMsg.Builder statusBuilder = WasheProto.ChannelWasheStatusRespMsg.newBuilder();
            statusBuilder.setType(1);
            statusBuilder.setModeId(matchData.getModeId());
            sendGamePacket(GameProtocol.S_WASHE_STATUS_FROM_CROSS, statusBuilder, matchData);
        }

        sendGamePacket(ClientProtocol.U_WASHE_MATCH, builder, userKey);
    }

    public void friendQuickChatByCmd(WasheUserKey userKey, int chatId) {
        String key = getKey(userKey);
        int ret = friendQuickChat(key, chatId);

        WasheProto.WasheFriendQuickChatRespMsg.Builder builder = WasheProto.WasheFriendQuickChatRespMsg.newBuilder();
        builder.setRet(ret);
        sendGamePacket(ClientProtocol.U_WASHE_FRIEND_QUICK_CHAT, builder, userKey);
    }

    public void friendChangeCardByCmd(WasheUserKey userKey, int cardId, List<Integer> skillIdList) {
        String key = getKey(userKey);
        int ret = friendChangeCard(key, cardId, skillIdList);

        WasheProto.WasheFriendChangeCardRespMsg.Builder builder = WasheProto.WasheFriendChangeCardRespMsg.newBuilder();
        builder.setRet(ret);
        sendGamePacket(ClientProtocol.U_WASHE_FRIEND_CHANGE_CARD, builder, userKey);
    }

    public void friendReadyByCmd(WasheUserKey userKey, boolean ready) {
        String key = getKey(userKey);
        int ret = friendReady(key, ready);

        WasheProto.WasheFriendReadyRespMsg.Builder builder = WasheProto.WasheFriendReadyRespMsg.newBuilder();
        builder.setRet(ret);
        sendGamePacket(ClientProtocol.U_WASHE_FRIEND_READY, builder, userKey);
    }

    public void friendChangeSceneByCmd(WasheUserKey userKey, int sceneId) {
        String key = getKey(userKey);
        int ret = friendChangeScene(key, sceneId);

        WasheProto.WasheFriendChangeSceneRespMsg.Builder builder = WasheProto.WasheFriendChangeSceneRespMsg.newBuilder();
        builder.setRet(ret);
        sendGamePacket(ClientProtocol.U_WASHE_FRIEND_CHANGE_SCENE, builder, userKey);
    }

    public int getDaySceneByCmd(WasheUserKey userKey) {
        // 检查0点刷新
        if (DateHelper.getTodayZeroTimeStamp() > sceneData.getRefreshTime()) {
            checkRefreshScene(false);
        }

        WasheProto.ChannelWasheSyncDaySceneRespMsg.Builder builder = WasheProto.ChannelWasheSyncDaySceneRespMsg.newBuilder();
        builder.addAllSceneId(sceneData.getSceneList());
        sendGamePacket(GameProtocol.S_WASHE_SYNC_DAY_SCENE, builder, userKey);
        return 0;
    }

    public void jinBiaoPeakJoinRoomByCmd(WasheUserKey userKey, WasheProto.ChannelWasheMatchReqMsg reqMsg, int round) {
        WasheUserMatchData matchData = parseWasheUserMatchData(userKey, reqMsg);
        int ret = jinBiaoPeakJoinRoom(matchData, round);
        if (ret != 0) {
            WasheProto.WasheFriendJoinRoomRespMsg.Builder builder = WasheProto.WasheFriendJoinRoomRespMsg.newBuilder();
            builder.setRet(ret);
            sendGamePacket(ClientProtocol.U_WASHE_FRIEND_JOIN_ROOM, builder, matchData);
        }
    }

    private int jinBiaoPeakJoinRoom(WasheUserMatchData matchData, int round) {
        synchronized (peakRoomDataMap) {
            // 正在匹配中 || 正在房间中
            if (matchingUserMap.containsKey(matchData.getKey()) || roomUserMap.containsKey(matchData.getKey())) {
                return getStatusErrorCode(matchData.getKey());
            }
            // 判断下是否有资格
            List<WasheJinBiaoPeakBattleData> battleDataList = peakRoomDataMap.get(round);
            if (battleDataList == null) {
                return GameErrorCode.E_WASHE_ROOM_NO_EXIST;
            }
            // 获取房间
            WasheRoomData roomData = null;
            // 判断下是否房间内的用户
            WasheJinBiaoPeakBattleData battleData = null;
            for (WasheJinBiaoPeakBattleData data : battleDataList) {
                WasheRoomData dataRoomData = data.getRoomData();
                if (dataRoomData == null) {
                    continue;
                }
                if (data.getUserId1() == matchData.getUserId() || data.getUserId2() == matchData.getUserId()) {
                    roomData = dataRoomData;
                    battleData = data;
                    break;
                }
            }
            // 没有对局拦一下
            if (battleData == null) {
                return GameErrorCode.E_WASHE_ROOM_NO_EXIST;
            }
            // 检测下房间状态, 已出结果不能进
            if (battleData.getBattleResult() != eWasheJinBiaoPeakBattleResultType.NotStart.getValue()) {
                return GameErrorCode.E_WASHE_ROOM_NO_EXIST;
            }
            if (roomData.getModeId() != eWasheModeType.JinBiaoPeak.getValue()) {
                return GameErrorCode.E_WASHE_ROOM_IS_NOT_PEAK_PK;
            }
            boolean isPlayer1 = false;
            if (matchData.getUserId() == battleData.getUserId1()) {
                isPlayer1 = true;
            }
            // 加入房间
            synchronized (roomData) {
                int ret = joinPeakRoom(roomData, matchData, isPlayer1);
                if (ret != 0) {
                    return ret;
                }
            }

            // 返回用户数据，这里要从积分榜取用户数据
            WasheProto.WasheJinBiaoPeakJoinRoomRespMsg.Builder builder = WasheProto.WasheJinBiaoPeakJoinRoomRespMsg.newBuilder();
            builder.setRet(0);
            builder.setRoomId(String.valueOf(roomData.getRoomId()));
            // 玩家1信息
            if (battleData.getUserId1() != 0) {
                long userId = battleData.getUserId1();
                builder.setPlayer1(parseWasheJinBiaoPeakBattleUserTemp(userId, roomData));
            }
            // 玩家2信息
            if (battleData.getUserId2() != 0) {
                long userId = battleData.getUserId2();
                builder.setPlayer2(parseWasheJinBiaoPeakBattleUserTemp(userId, roomData));
            }
            sendGamePacket(ClientProtocol.U_WASHE_GET_JIN_BIAO_PEAK_JOIN_ROOM, builder, matchData);

            // 同步
            WasheProto.WasheJinBiaoPeakSyncRoomRespMsg.Builder syncBuilder = WasheProto.WasheJinBiaoPeakSyncRoomRespMsg.newBuilder();
            syncBuilder.setType(eWasheJinBiaoPeakNoticeType.PlayerJoinRoom.getValue());
            syncBuilder.setUser(parseWasheJinBiaoPeakBattleUserTemp(matchData.getUserId(), roomData));
            syncBuilder.setArg(matchData.getCardId());
            List<WasheUserGameData> list = new ArrayList<>();
            WasheUserGameData player1 = roomData.getPlayer1();
            if (player1 != null) {
                list.add(player1);
            }
            WasheUserGameData player2 = roomData.getPlayer2();
            if (player2 != null) {
                list.add(player2);
            }
            for (WasheUserGameData tmp : list) {
                sendGamePacket(ClientProtocol.U_WASHE_JIN_BIAO_PEAK_SYNC_ROOM, syncBuilder, tmp.getUserMatchData());
            }
            return 0;
        }
    }

    public WasheProto.WasheJinBiaoPeakBattleUserTemp.Builder parseWasheJinBiaoPeakBattleUserTemp(long userId, WasheRoomData roomData) {
        // 取玩家数据
        XSeasonUserRank userRank = peakXSeasonUserRankMap.get(userId);
        if (userRank == null) {
            return null;
        }
        WasheProto.WasheJinBiaoPeakBattleUserTemp.Builder player = WasheProto.WasheJinBiaoPeakBattleUserTemp.newBuilder();
        player.setUserId(userId);
        player.setPlayerInfo(PlayerBasePb.parsePlayerBaseTempMsg(userRank.getBaseInfo()));
        player.setChannelId(userRank.getChannelId());
        player.setServerName(ChannelXServerNameMgr.getServerNameMap().getServerName(userRank.getChannelId(), userRank.getServerId()));
        boolean isReady = false;
        int cardId = 0;
        List<Integer> skillIdList = new ArrayList<>();
        if (roomData != null && roomData.getPlayer1() != null && roomData.getPlayer1().getUserMatchData() != null && roomData.getPlayer1().getUserMatchData().getUserId() == userId) {
            isReady = roomData.getPlayer1().isReady();
            cardId = roomData.getPlayer1().getUserMatchData().getCardId();
            skillIdList = roomData.getPlayer1().getUserMatchData().getSkills();
        } else if (roomData != null && roomData.getPlayer2() != null && roomData.getPlayer2().getUserMatchData() != null && roomData.getPlayer2().getUserMatchData().getUserId() == userId) {
            isReady = roomData.getPlayer2().isReady();
            cardId = roomData.getPlayer2().getUserMatchData().getCardId();
            skillIdList = roomData.getPlayer1().getUserMatchData().getSkills();
        }
        player.setReady(isReady);
        player.setCardId(cardId);
        player.addAllSkillId(skillIdList);
        return player;
    }


    public int getJinBiaoPeakData(WasheUserKey userKey, int round) {
        WasheRoundConfig roundConfig = config.getRoundConfigMap().get(round);
        WasheSeasonConfig seasonConfig = config.getWasheSeasonConfig();
        if (roundConfig == null || seasonConfig == null) {
            return GameErrorCode.E_WASHE_JIN_BIAO_NO_ROUND_DATA;
        }
        Set<Integer> canShowRoundList = new HashSet<>();
        long nowTime = System.currentTimeMillis();
        if (nowTime > seasonConfig.getPeakednessBeginTime()) {
            // 首轮在巅峰赛赛期开始后公布；
            canShowRoundList.add(1);
        }
        for (WasheRoundConfig tempRoundConfig : config.getRoundConfigMap().values()) {
            int curRound = tempRoundConfig.getRound();
            // 后面轮次在上一轮开战后就公布
            if (nowTime > tempRoundConfig.getWarTime()) {
                canShowRoundList.add(curRound);
                // 下一回合也加进来
                if (config.getRoundConfigMap().containsKey(curRound + 1)) {
                    canShowRoundList.add(curRound + 1);
                }
            }
        }
        // 未开放的显示空数组

        List<WasheJinBiaoPeakBattleData> peakBattleData = new ArrayList<>();
        if (canShowRoundList.contains(round) && !peakXSeasonUserRankMap.isEmpty()) {
            peakBattleData = peakRoomDataMap.getOrDefault(round, new ArrayList<>());
        }
        WasheProto.WasheGetJinBiaoPeakDataRespMsg.Builder builder = WasheProto.WasheGetJinBiaoPeakDataRespMsg.newBuilder();
        for (WasheJinBiaoPeakBattleData battleData : peakBattleData) {
            // 对局信息
            WasheProto.WasheJinBiaoPeakRoomTemp.Builder roomTemp = WasheProto.WasheJinBiaoPeakRoomTemp.newBuilder();
            if (battleData.getRoomData() != null) {
                roomTemp.setRoomId(String.valueOf(battleData.getRoomData().getRoomId()));
            }
            // 玩家1信息
            if (battleData.getUserId1() != 0) {
                roomTemp.setPlayer1(parseWasheJinBiaoPeakBattleUserTemp(battleData.getUserId1(), battleData.getRoomData()));
            }
            // 玩家2信息
            if (battleData.getUserId2() != 0) {
                roomTemp.setPlayer2(parseWasheJinBiaoPeakBattleUserTemp(battleData.getUserId2(), battleData.getRoomData()));
            }
            roomTemp.setBattleResult(battleData.getBattleResult());
            builder.addBattle(roomTemp);
        }
        builder.setRet(0);
        builder.setRound(round);
        builder.setSceneId(roundConfig.getSceneSkillId());
        sendGamePacket(ClientProtocol.U_WASHE_GET_JIN_BIAO_PEAK_DATA, builder, userKey);
        return 0;
    }

    public int getJinBiaoPeakBattleRecord(WasheUserKey userKey, long userId, long serverId) {
        WasheProto.WashePeakBattleRecordRespMsg.Builder builder = WasheProto.WashePeakBattleRecordRespMsg.newBuilder();
        builder.setRet(0);
        for (List<WasheJinBiaoPeakBattleData> battleList : peakRoomDataMap.values()) {
            for (WasheJinBiaoPeakBattleData battleData : battleList) {
                boolean isPlayer1 = battleData.getUserId1() == userId && battleData.getServerId1() == serverId;
                boolean isPlayer2 = battleData.getUserId2() == userId && battleData.getServerId2() == serverId;
                if (!isPlayer1 && !isPlayer2) {
                    continue;
                }
                int type = 0;
                if (battleData.getBattleResult() == eWasheJinBiaoPeakBattleResultType.NotStart.getValue() || battleData.getBattleResult() == eWasheJinBiaoPeakBattleResultType.InBattle.getValue()) {
                    continue;
                } else if (battleData.getBattleResult() == eWasheJinBiaoPeakBattleResultType.NoWin.getValue()) {
                    type = 3;
                } else if (battleData.getBattleResult() == eWasheJinBiaoPeakBattleResultType.GiveUp.getValue()) {
                    type = 4;
                } else {
                    type = isPlayer1 ?
                            battleData.getBattleResult() == eWasheJinBiaoPeakBattleResultType.Player1Win.getValue() ? 1 : 2
                            : battleData.getBattleResult() == eWasheJinBiaoPeakBattleResultType.Player2Win.getValue() ? 1 : 2;
                }
                WasheProto.WasheJinBiaoPeakBattleRecordTemp.Builder temp = WasheProto.WasheJinBiaoPeakBattleRecordTemp.newBuilder();
                temp.setRound(battleData.getRound());
                temp.setBattleResult(type);
                temp.setScore(isPlayer1 ? battleData.getScore1() : battleData.getScore2());
                builder.addRecord(temp);
            }
        }
        sendGamePacket(ClientProtocol.U_WASHE_GET_PEAK_BATTLE_RECORD, builder, userKey);
        return 0;
    }


    public void jinBiaoPeakChangeCardByCmd(WasheUserKey userKey, int cardId, List<Integer> skillIdList) {
        String key = getKey(userKey);
        int ret = jinBiaoPeakChangeCard(key, cardId, skillIdList);

        WasheProto.WasheJinBiaoPeakChangeCardRespMsg.Builder builder = WasheProto.WasheJinBiaoPeakChangeCardRespMsg.newBuilder();
        builder.setRet(ret);
        sendGamePacket(ClientProtocol.U_WASHE_JIN_BIAO_PEAK_CHANGE_CARD, builder, userKey);
    }

    private int jinBiaoPeakChangeCard(String key, int cardId, List<Integer> skillIdList) {
        WasheUserMatchData matchData = roomUserMap.get(key);
        if (matchData == null) {
            return getNoStatusErrorCode();
        }
        WasheRoomData roomData = roomMap.get(matchData.getRoomId());
        if (roomData == null) {
            return GameErrorCode.E_WASHE_ROOM_NO_EXIST;
        }
        if (roomData.getModeId() != eWasheModeType.JinBiaoPeak.getValue()) {
            return GameErrorCode.E_WASHE_ROOM_IS_NOT_PEAK_PK;
        }

        boolean isPlayer1;
        WasheUserGameData userGameData;
        synchronized (roomData) {
            if (roomData.isBegin()) {
                return GameErrorCode.E_WASHE_GAME_START;
            }

            isPlayer1 = isPlayer1(roomData, key);
            userGameData = isPlayer1 ? roomData.getPlayer1() : roomData.getPlayer2();

            // 换卡片
            WasheCardConfig cardConfig = config.getCardConfigMap().get(cardId);
            userGameData.getUserMatchData().setCardId(cardConfig.getCardId());
            userGameData.setCard(new WasheUserGameCard(cardConfig.getCardId(), 0, cardConfig.getCd()));
            // 换技能
            userGameData.getUserMatchData().setSkills(skillIdList);
            List<WasheUserGameSkill> skillList = new ArrayList<>();
            for (int skillId : skillIdList) {
                skillList.add(new WasheUserGameSkill(skillId, config.getSkillUseTimes()));
            }
            userGameData.setSkillList(skillList);
        }

        // 同步
        WasheProto.WasheJinBiaoPeakSyncRoomRespMsg.Builder syncBuilder = WasheProto.WasheJinBiaoPeakSyncRoomRespMsg.newBuilder();
        syncBuilder.setType(eWasheJinBiaoPeakNoticeType.PlayerChangeCard.getValue());
        syncBuilder.setArg(cardId);
        syncBuilder.setUser(parseWasheJinBiaoPeakBattleUserTemp(matchData.getUserId(), roomData));
        List<WasheUserGameData> list = new ArrayList<>();
        WasheUserGameData player1 = roomData.getPlayer1();
        if (player1 != null) {
            list.add(player1);
        }
        WasheUserGameData player2 = roomData.getPlayer2();
        if (player2 != null) {
            list.add(player2);
        }
        for (WasheUserGameData tmp : list) {
            sendGamePacket(ClientProtocol.U_WASHE_JIN_BIAO_PEAK_SYNC_ROOM, syncBuilder, tmp.getUserMatchData());
        }
        return 0;
    }

    public void jinBiaoPeakLeaveRoomByCmd(WasheUserKey userKey) {
        String key = getKey(userKey);
        int ret = jinBiaoPeakLeaveRoom(key);

        WasheProto.WasheJinBiaoPeakLeaveRoomRespMsg.Builder builder = WasheProto.WasheJinBiaoPeakLeaveRoomRespMsg.newBuilder();
        builder.setRet(ret);
        sendGamePacket(ClientProtocol.U_WASHE_JIN_BIAO_PEAK_LEAVE_ROOM, builder, userKey);
    }

    private int jinBiaoPeakLeaveRoom(String key) {
        synchronized (peakRoomDataMap) {
            WasheUserMatchData matchData = roomUserMap.get(key);
            if (matchData == null) {
                return 0;
            }
            WasheRoomData roomData = roomMap.get(matchData.getRoomId());
            if (roomData == null) {
                return 0;
            }
            if (roomData.getModeId() != eWasheModeType.JinBiaoPeak.getValue()) {
                return GameErrorCode.E_WASHE_ROOM_IS_NOT_PEAK_PK;
            }

            synchronized (roomData) {
                if (roomData.isBegin()) {
                    return GameErrorCode.E_WASHE_GAME_START;
                }
                boolean isPlayer1 = isPlayer1(roomData, key);
                // 离开
                if (isPlayer1) {
                    WasheUserGameData player1 = roomData.getPlayer1();
                    if (player1 != null) {
                        roomData.setPlayer1(null);
                        roomUserMap.remove(player1.getUserMatchData().getKey());
                    }
                } else {
                    WasheUserGameData player2 = roomData.getPlayer2();
                    if (player2 != null) {
                        roomData.setPlayer2(null);
                        roomUserMap.remove(player2.getUserMatchData().getKey());
                    }
                }
                // 同步
                List<WasheUserGameData> list = new ArrayList<>();
                WasheUserGameData player1 = roomData.getPlayer1();
                if (player1 != null) {
                    list.add(player1);
                }
                WasheUserGameData player2 = roomData.getPlayer2();
                if (player2 != null) {
                    list.add(player2);
                }
                WasheProto.WasheJinBiaoPeakSyncRoomRespMsg.Builder syncBuilder = WasheProto.WasheJinBiaoPeakSyncRoomRespMsg.newBuilder();
                syncBuilder.setType(eWasheJinBiaoPeakNoticeType.PlayerQuitRoom.getValue());
                syncBuilder.setUser(parseWasheJinBiaoPeakBattleUserTemp(matchData.getUserId(), roomData));
                for (WasheUserGameData tmp : list) {
                    sendGamePacket(ClientProtocol.U_WASHE_JIN_BIAO_PEAK_SYNC_ROOM, syncBuilder, tmp.getUserMatchData());
                }
            }
        }
        return 0;
    }

    public void jinBiaoPeakReturnRoomByCmd(WasheUserKey userKey) {
        int ret = jinBiaoPeakReturnRoom(getKey(userKey));
        if (ret != 0) {
            WasheProto.WasheJinBiaoPeakEnterMyRoomRespMsg.Builder builder = WasheProto.WasheJinBiaoPeakEnterMyRoomRespMsg.newBuilder();
            sendGamePacket(ClientProtocol.U_WASHE_JIN_BIAO_PEAK_ENTER_MY_ROOM, builder, userKey);
        }
    }

    private int jinBiaoPeakReturnRoom(String key) {
        WasheUserMatchData matchData = roomUserMap.get(key);
        if (matchData == null) {
            return getNoStatusErrorCode();
        }
        synchronized (peakRoomDataMap) {
            // 获取当前轮次
            int round = config.getCurPeakRound();
            List<WasheJinBiaoPeakBattleData> battleDataList = peakRoomDataMap.get(round);
            if (battleDataList == null) {
                return GameErrorCode.E_WASHE_ROOM_NO_EXIST;
            }
            // 获取房间
            WasheRoomData roomData = null;
            // 判断下是否房间内的用户
            WasheJinBiaoPeakBattleData battleData = null;
            for (WasheJinBiaoPeakBattleData data : battleDataList) {
                WasheRoomData dataRoomData = data.getRoomData();
                if (dataRoomData == null) {
                    continue;
                }
                if (data.getUserId1() == matchData.getUserId() || data.getUserId2() == matchData.getUserId()) {
                    roomData = dataRoomData;
                    battleData = data;
                    break;
                }
            }
            if (battleData == null) {
                return GameErrorCode.E_WASHE_ROOM_NO_EXIST;
            }
            if (roomData.getModeId() != eWasheModeType.JinBiaoPeak.getValue()) {
                return GameErrorCode.E_WASHE_ROOM_IS_NOT_PEAK_PK;
            }
            if (roomData.isBegin()) {
                return GameErrorCode.E_WASHE_ROOM_NO_EXIST;
            }
            if (roomData.getPlayer1() == null && roomData.getPlayer2() == null) {
                return GameErrorCode.E_WASHE_ROOM_NO_EXIST;
            }
            boolean isPlayer1;
            synchronized (roomData) {
                isPlayer1 = isPlayer1(roomData, key);
                WasheUserGameData userGameData = isPlayer1 ? roomData.getPlayer1() : roomData.getPlayer2();
                userGameData.setStepOutTime(0);
            }

            // 返回
            WasheProto.WasheJinBiaoPeakEnterMyRoomRespMsg.Builder builder = WasheProto.WasheJinBiaoPeakEnterMyRoomRespMsg.newBuilder();
            builder.setRoomId(String.valueOf(roomData.getRoomId()));
            if (battleData.getUserId1() != 0) {
                builder.setPlayer1(parseWasheJinBiaoPeakBattleUserTemp(battleData.getUserId1(), roomData));
            }
            if (battleData.getUserId2() != 0) {
                builder.setPlayer2(parseWasheJinBiaoPeakBattleUserTemp(battleData.getUserId2(), roomData));
            }
            sendGamePacket(ClientProtocol.U_WASHE_JIN_BIAO_PEAK_ENTER_MY_ROOM, builder, matchData);
        }
        return 0;
    }

    /**
     * 获取锦标赛阶段性奖励信息
     */
    public int getJinBiaoScoreStageRewardInfo(WasheUserKey userKey, List<Integer> hadRewardStageList) {
        WasheSeasonConfig seasonConfig = config.getWasheSeasonConfig();
        if (seasonConfig == null) {
            return GameErrorCode.E_WASHE_JIN_BIAO_NO_ROUND_DATA;
        }

        WasheProto.WasheJinBiaoScoreStageRewardInfoRespMsg.Builder builder = WasheProto.WasheJinBiaoScoreStageRewardInfoRespMsg.newBuilder();
        XSeasonStageUserRankMap seasonStageUserRankMap = ChannelXRankMgr.getSeasonStageUserRankMap();
        List<String> stageConditionParams = StringUtils.stringToStringList(config.getJinBiaoScoreStageCondition(), "\\|");
        for (int i = 0; i < stageConditionParams.size(); i++) {
            int stage = i + 1;
            String[] split = stageConditionParams.get(i).split(",");
            int beginTime = Integer.parseInt(split[0]);
            int maxRank = Integer.parseInt(split[1]);
            if (seasonConfig.getIntegralBeginTime() + DateHelper.SECOND_MILLIONS * beginTime > System.currentTimeMillis()) {
                continue;
            }
            WasheProto.WasheJinBiaoScoreStageTemp.Builder temp = WasheProto.WasheJinBiaoScoreStageTemp.newBuilder();
            int myRank = -1;
            if (seasonStageUserRankMap != null) {
                XSeasonStageUserRankListModel userRankListModel = seasonStageUserRankMap.getUserRankListModel(eSystemId.WasheActivity.getValue(), config.getSeasonId(), eSeasonRewardType.JinBiaoScoreRank.getIntValue(), stage, SeasonHelper.getWasheRangeKey(config.getActivityInfo().getParam6()));
                if (userRankListModel != null) {
                    myRank = userRankListModel.getMyRank(userKey.getUserId());
                }
                if (myRank > maxRank) {
                    myRank = -1;
                }
            }
            temp.setStage(stage);
            temp.setRankNum(myRank);
            // 是否领奖
            temp.setIsReward(hadRewardStageList.contains(stage));
            builder.addStage(temp);
        }
        builder.setRet(0);
        sendGamePacket(ClientProtocol.U_WASHE_GET_STAGE_REWARD_INFO, builder, userKey);
        return 0;
    }

    /**
     * 获取锦标赛阶段性奖励信息
     */
    public int getJinBiaoScoreStageReward(WasheUserKey userKey) {
        WasheSeasonConfig seasonConfig = config.getWasheSeasonConfig();
        if (seasonConfig == null) {
            return GameErrorCode.E_WASHE_JIN_BIAO_NO_ROUND_DATA;
        }
        WasheProto.ChannelWasheGetStageRewardRespMsg.Builder builder = WasheProto.ChannelWasheGetStageRewardRespMsg.newBuilder();
        XSeasonStageUserRankMap seasonStageUserRankMap = ChannelXRankMgr.getSeasonStageUserRankMap();
        List<String> stageConditionParams = StringUtils.stringToStringList(config.getJinBiaoScoreStageCondition(), "\\|");
        for (int i = 0; i < stageConditionParams.size(); i++) {
            int stage = i + 1;
            String[] split = stageConditionParams.get(i).split(",");
            int beginTime = Integer.parseInt(split[0]);
            int maxRank = Integer.parseInt(split[1]);
            if (seasonConfig.getIntegralBeginTime() + DateHelper.SECOND_MILLIONS * beginTime > System.currentTimeMillis()) {
                continue;
            }
            WasheProto.ChannelWasheJinBiaoScoreStageTemp.Builder temp = WasheProto.ChannelWasheJinBiaoScoreStageTemp.newBuilder();
            int myRank = -1;
            if (seasonStageUserRankMap != null) {
                XSeasonStageUserRankListModel userRankListModel = seasonStageUserRankMap.getUserRankListModel(eSystemId.WasheActivity.getValue(), config.getSeasonId(), eSeasonRewardType.JinBiaoScoreRank.getIntValue(), stage, SeasonHelper.getWasheRangeKey(config.getActivityInfo().getParam6()));
                if (userRankListModel != null) {
                    myRank = userRankListModel.getMyRank(userKey.getUserId());
                }
                if (myRank > maxRank) {
                    myRank = -1;
                }
            }
            temp.setStage(stage);
            temp.setRankNum(myRank);
            builder.addStage(temp);
        }
        sendGamePacket(GameProtocol.S_WASHE_GET_STAGE_REWARD_FROM_CROSS, builder, userKey);
        return 0;
    }

    // --------------------------- getter --------------------------------

    public WasheConfig getConfig() {
        return config;
    }

    // --------------------------- save --------------------------------

    public void save() {
        try {
            for (List<WasheJinBiaoPeakBattleData> battleDataList : peakRoomDataMap.values()) {
                for (WasheJinBiaoPeakBattleData battleData : battleDataList) {
                    if (battleData.isInsertOption()) {
                        WasheBusiness.addWasheJinBiaoPeakBattleData(battleData);
                    } else if (battleData.isUpdateOption()) {
                        WasheBusiness.updateWasheJinBiaoPeakBattleData(battleData);
                    }
                }
            }
        } catch (Exception e) {
            logger.error("error : ", e);
        }

        if (sceneData != null) {
            if (sceneData.isInsertOption()) {
                WasheBusiness.addWasheSceneData(sceneData);
            } else if (sceneData.isUpdateOption()) {
                WasheBusiness.updateWasheSceneData(sceneData);
            }
        }
    }

}
